From ac5d74d42c0824b5789cc030bf39a447951e4804 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 21 Mar 2015 21:55:02 +0100 Subject: web: fix bugs --- libmproxy/web/static/app.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index dae10a34..ef53c08f 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -421,6 +421,7 @@ module.exports = { ConnectionActions: ConnectionActions, FlowActions: FlowActions, StoreCmds: StoreCmds, + SettingsActions: SettingsActions, Query: Query }; @@ -622,6 +623,7 @@ var common = require("./common.js"); var Query = require("../actions.js").Query; var VirtualScrollMixin = require("./virtualscroll.js"); var views = require("../store/view.js"); +var _ = require("lodash"); var LogMessage = React.createClass({displayName: "LogMessage", render: function () { @@ -775,7 +777,7 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); var _ = require("lodash"); @@ -1774,7 +1776,7 @@ var MainMenu = React.createClass({displayName: "MainMenu", this.setQuery(d); }, onInterceptChange: function (val) { - SettingsActions.update({intercept: val}); + actions.SettingsActions.update({intercept: val}); }, render: function () { var filter = this.getQuery()[Query.FILTER] || ""; @@ -2196,6 +2198,8 @@ var MainView = React.createClass({displayName: "MainView", actions.FlowActions.revert(flow); } break; + case toputils.Key.SHIFT: + break; default: console.debug("keydown", e.keyCode); return; @@ -4514,8 +4518,6 @@ var default_filt = function (elem) { function StoreView(store, filt, sortfun) { EventEmitter.call(this); - filt = filt || default_filt; - sortfun = sortfun || default_sort; this.store = store; @@ -4539,10 +4541,10 @@ _.extend(StoreView.prototype, EventEmitter.prototype, { this.store.removeListener("recalculate", this.recalculate); }, recalculate: function (filt, sortfun) { - filt = filt || default_filt; - sortfun = sortfun || default_sort; + filt = filt || this.filt || default_filt; + sortfun = sortfun || this.sortfun || default_sort; filt = filt.bind(this); - sortfun = sortfun.bind(this) + sortfun = sortfun.bind(this); this.filt = filt; this.sortfun = sortfun; @@ -4633,6 +4635,7 @@ var Key = { TAB: 9, SPACE: 32, BACKSPACE: 8, + SHIFT: 16 }; // Add A-Z for (var i = 65; i <= 90; i++) { -- cgit v1.2.3 From 02a61ea45dc1ca6d0c88b44adf83f68b791130e7 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 21 Mar 2015 22:49:51 +0100 Subject: structure components --- libmproxy/web/static/app.js | 1031 ++++++++++++++++++++++--------------------- 1 file changed, 528 insertions(+), 503 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index ef53c08f..04d6f282 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -443,7 +443,7 @@ $(function () { -},{"./components/proxyapp.js":12,"./connection":14,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ +},{"./components/proxyapp.js":15,"./connection":17,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -777,156 +777,399 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":22,"./common.js":4,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); -var _ = require("lodash"); - -var common = require("./common.js"); -var actions = require("../actions.js"); -var flowutils = require("../flow/utils.js"); -var toputils = require("../utils.js"); +var RequestUtils = require("../flow/utils.js").RequestUtils; +var ResponseUtils = require("../flow/utils.js").ResponseUtils; +var utils = require("../utils.js"); -var NavAction = React.createClass({displayName: "NavAction", - onClick: function (e) { - e.preventDefault(); - this.props.onClick(); +var TLSColumn = React.createClass({displayName: "TLSColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-tls " + (this.props.className || "") })); + } + }), + sortKeyFun: function(flow){ + return flow.request.scheme; + } }, render: function () { - return ( - React.createElement("a", {title: this.props.title, - href: "#", - className: "nav-action", - onClick: this.onClick}, - React.createElement("i", {className: "fa fa-fw " + this.props.icon}) - ) - ); + var flow = this.props.flow; + var ssl = (flow.request.scheme == "https"); + var classes; + if (ssl) { + classes = "col-tls col-tls-https"; + } else { + classes = "col-tls col-tls-http"; + } + return React.createElement("td", {className: classes}); } }); -var FlowDetailNav = React.createClass({displayName: "FlowDetailNav", + +var IconColumn = React.createClass({displayName: "IconColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-icon " + (this.props.className || "") })); + } + }) + }, render: function () { var flow = this.props.flow; - var tabs = this.props.tabs.map(function (e) { - var str = e.charAt(0).toUpperCase() + e.slice(1); - var className = this.props.active === e ? "active" : ""; - var onClick = function (event) { - this.props.selectTab(e); - event.preventDefault(); - }.bind(this); - return React.createElement("a", {key: e, - href: "#", - className: className, - onClick: onClick}, str); - }.bind(this)); + var icon; + if (flow.response) { + var contentType = ResponseUtils.getContentType(flow.response); - var acceptButton = null; - if(flow.intercepted){ - acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)}); + //TODO: We should assign a type to the flow somewhere else. + if (flow.response.code == 304) { + icon = "resource-icon-not-modified"; + } else if (300 <= flow.response.code && flow.response.code < 400) { + icon = "resource-icon-redirect"; + } else if (contentType && contentType.indexOf("image") >= 0) { + icon = "resource-icon-image"; + } else if (contentType && contentType.indexOf("javascript") >= 0) { + icon = "resource-icon-js"; + } else if (contentType && contentType.indexOf("css") >= 0) { + icon = "resource-icon-css"; + } else if (contentType && contentType.indexOf("html") >= 0) { + icon = "resource-icon-document"; + } } - var revertButton = null; - if(flow.modified){ - revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)}); + if (!icon) { + icon = "resource-icon-plain"; } - return ( - React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"}, - tabs, - React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}), - React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}), - React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}), - acceptButton, - revertButton - ) + + icon += " resource-icon"; + return React.createElement("td", {className: "col-icon"}, + React.createElement("div", {className: icon}) ); } }); -var Headers = React.createClass({displayName: "Headers", +var PathColumn = React.createClass({displayName: "PathColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-path " + (this.props.className || "") }), "Path"); + } + }), + sortKeyFun: function(flow){ + return RequestUtils.pretty_url(flow.request); + } + }, render: function () { - var rows = this.props.message.headers.map(function (header, i) { - return ( - React.createElement("tr", {key: i}, - React.createElement("td", {className: "header-name"}, header[0] + ":"), - React.createElement("td", {className: "header-value"}, header[1]) - ) - ); - }); - return ( - React.createElement("table", {className: "header-table"}, - React.createElement("tbody", null, - rows - ) - ) + var flow = this.props.flow; + return React.createElement("td", {className: "col-path"}, + flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null, + flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null, + RequestUtils.pretty_url(flow.request) ); } }); -var FlowDetailRequest = React.createClass({displayName: "FlowDetailRequest", + +var MethodColumn = React.createClass({displayName: "MethodColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-method " + (this.props.className || "") }), "Method"); + } + }), + sortKeyFun: function(flow){ + return flow.request.method; + } + }, render: function () { var flow = this.props.flow; - var first_line = [ - flow.request.method, - flowutils.RequestUtils.pretty_url(flow.request), - "HTTP/" + flow.request.httpversion.join(".") - ].join(" "); - var content = null; - if (flow.request.contentLength > 0) { - content = "Request Content Size: " + toputils.formatSize(flow.request.contentLength); + return React.createElement("td", {className: "col-method"}, flow.request.method); + } +}); + + +var StatusColumn = React.createClass({displayName: "StatusColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-status " + (this.props.className || "") }), "Status"); + } + }), + sortKeyFun: function(flow){ + return flow.response ? flow.response.code : undefined; + } + }, + render: function () { + var flow = this.props.flow; + var status; + if (flow.response) { + status = flow.response.code; } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + status = null; } + return React.createElement("td", {className: "col-status"}, status); + } +}); - //TODO: Styling - return ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), - React.createElement(Headers, {message: flow.request}), - React.createElement("hr", null), - content - ) - ); +var SizeColumn = React.createClass({displayName: "SizeColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-size " + (this.props.className || "") }), "Size"); + } + }), + sortKeyFun: function(flow){ + var total = flow.request.contentLength; + if (flow.response) { + total += flow.response.contentLength || 0; + } + return total; + } + }, + render: function () { + var flow = this.props.flow; + + var total = flow.request.contentLength; + if (flow.response) { + total += flow.response.contentLength || 0; + } + var size = utils.formatSize(total); + return React.createElement("td", {className: "col-size"}, size); } }); -var FlowDetailResponse = React.createClass({displayName: "FlowDetailResponse", + +var TimeColumn = React.createClass({displayName: "TimeColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-time " + (this.props.className || "") }), "Time"); + } + }), + sortKeyFun: function(flow){ + if(flow.response) { + return flow.response.timestamp_end - flow.request.timestamp_start; + } + } + }, render: function () { var flow = this.props.flow; - var first_line = [ - "HTTP/" + flow.response.httpversion.join("."), - flow.response.code, - flow.response.msg - ].join(" "); - var content = null; - if (flow.response.contentLength > 0) { - content = "Response Content Size: " + toputils.formatSize(flow.response.contentLength); + var time; + if (flow.response) { + time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + time = "..."; } + return React.createElement("td", {className: "col-time"}, time); + } +}); - //TODO: Styling + +var all_columns = [ + TLSColumn, + IconColumn, + PathColumn, + MethodColumn, + StatusColumn, + SizeColumn, + TimeColumn +]; + +module.exports = all_columns; + +},{"../flow/utils.js":20,"../utils.js":23,"react":"react"}],7:[function(require,module,exports){ +var React = require("react"); +var common = require("./common.js"); +var utils = require("../utils.js"); +var _ = require("lodash"); + +var VirtualScrollMixin = require("./virtualscroll.js"); +var flowtable_columns = require("./flowtable-columns.js"); + +var FlowRow = React.createClass({displayName: "FlowRow", + render: function () { + var flow = this.props.flow; + var columns = this.props.columns.map(function (Column) { + return React.createElement(Column, {key: Column.displayName, flow: flow}); + }.bind(this)); + var className = ""; + if (this.props.selected) { + className += " selected"; + } + if (this.props.highlighted) { + className += " highlighted"; + } + if (flow.intercepted) { + className += " intercepted"; + } + if (flow.request) { + className += " has-request"; + } + if (flow.response) { + className += " has-response"; + } return ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), - React.createElement(Headers, {message: flow.response}), - React.createElement("hr", null), - content - ) + React.createElement("tr", {className: className, onClick: this.props.selectFlow.bind(null, flow)}, + columns + )); + }, + shouldComponentUpdate: function (nextProps) { + return true; + // Further optimization could be done here + // by calling forceUpdate on flow updates, selection changes and column changes. + //return ( + //(this.props.columns.length !== nextProps.columns.length) || + //(this.props.selected !== nextProps.selected) + //); + } +}); + +var FlowTableHead = React.createClass({displayName: "FlowTableHead", + getInitialState: function(){ + return { + sortColumn: undefined, + sortDesc: false + }; + }, + onClick: function(Column){ + var sortDesc = this.state.sortDesc; + var hasSort = Column.sortKeyFun; + if(Column === this.state.sortColumn){ + sortDesc = !sortDesc; + this.setState({ + sortDesc: sortDesc + }); + } else { + this.setState({ + sortColumn: hasSort && Column, + sortDesc: false + }) + } + var sortKeyFun; + if(!sortDesc){ + sortKeyFun = Column.sortKeyFun; + } else { + sortKeyFun = hasSort && function(){ + var k = Column.sortKeyFun.apply(this, arguments); + if(_.isString(k)){ + return utils.reverseString(""+k); + } else { + return -k; + } + } + } + this.props.setSortKeyFun(sortKeyFun); + }, + render: function () { + var columns = this.props.columns.map(function (Column) { + var onClick = this.onClick.bind(this, Column); + var className; + if(this.state.sortColumn === Column) { + if(this.state.sortDesc){ + className = "sort-desc"; + } else { + className = "sort-asc"; + } + } + return React.createElement(Column.Title, { + key: Column.displayName, + onClick: onClick, + className: className}); + }.bind(this)); + return React.createElement("thead", null, + React.createElement("tr", null, columns) + ); + } +}); + + +var ROW_HEIGHT = 32; + +var FlowTable = React.createClass({displayName: "FlowTable", + mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin], + getInitialState: function () { + return { + columns: flowtable_columns + }; + }, + _listen: function(view){ + if(!view){ + return; + } + view.addListener("add", this.onChange); + view.addListener("update", this.onChange); + view.addListener("remove", this.onChange); + view.addListener("recalculate", this.onChange); + }, + componentWillMount: function () { + this._listen(this.props.view); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.view !== this.props.view) { + if (this.props.view) { + this.props.view.removeListener("add"); + this.props.view.removeListener("update"); + this.props.view.removeListener("remove"); + this.props.view.removeListener("recalculate"); + } + this._listen(nextProps.view); + } + }, + getDefaultProps: function () { + return { + rowHeight: ROW_HEIGHT + }; + }, + onScrollFlowTable: function () { + this.adjustHead(); + this.onScroll(); + }, + onChange: function () { + this.forceUpdate(); + }, + scrollIntoView: function (flow) { + this.scrollRowIntoView( + this.props.view.index(flow), + this.refs.body.getDOMNode().offsetTop + ); + }, + renderRow: function (flow) { + var selected = (flow === this.props.selected); + var highlighted = + ( + this.props.view._highlight && + this.props.view._highlight[flow.id] + ); + + return React.createElement(FlowRow, {key: flow.id, + ref: flow.id, + flow: flow, + columns: this.state.columns, + selected: selected, + highlighted: highlighted, + selectFlow: this.props.selectFlow} ); - } -}); - -var FlowDetailError = React.createClass({displayName: "FlowDetailError", + }, render: function () { - var flow = this.props.flow; + //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); + var flows = this.props.view ? this.props.view.list : []; + + var rows = this.renderRows(flows); + return ( - React.createElement("section", null, - React.createElement("div", {className: "alert alert-warning"}, - flow.error.msg, - React.createElement("div", null, - React.createElement("small", null, toputils.formatTimeStamp(flow.error.timestamp) ) + React.createElement("div", {className: "flow-table", onScroll: this.onScrollFlowTable}, + React.createElement("table", null, + React.createElement(FlowTableHead, {ref: "head", + columns: this.state.columns, + setSortKeyFun: this.props.setSortKeyFun}), + React.createElement("tbody", {ref: "body"}, + this.getPlaceholderTop(flows.length), + rows, + this.getPlaceholderBottom(flows.length) ) ) ) @@ -934,6 +1177,15 @@ var FlowDetailError = React.createClass({displayName: "FlowDetailError", } }); +module.exports = FlowTable; + + +},{"../utils.js":23,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var utils = require("../../utils.js"); + var TimeStamp = React.createClass({displayName: "TimeStamp", render: function () { @@ -942,11 +1194,11 @@ var TimeStamp = React.createClass({displayName: "TimeStamp", return React.createElement("tr", null); } - var ts = toputils.formatTimeStamp(this.props.t); + var ts = utils.formatTimeStamp(this.props.t); var delta; if (this.props.deltaTo) { - delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); + delta = utils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); delta = React.createElement("span", {className: "text-muted"}, "(" + delta + ")"); } else { delta = null; @@ -1086,7 +1338,7 @@ var Timing = React.createClass({displayName: "Timing", } }); -var FlowDetailConnectionInfo = React.createClass({displayName: "FlowDetailConnectionInfo", +var Details = React.createClass({displayName: "Details", render: function () { var flow = this.props.flow; var client_conn = flow.client_conn; @@ -1109,14 +1361,25 @@ var FlowDetailConnectionInfo = React.createClass({displayName: "FlowDetailConnec } }); +module.exports = Details; + +},{"../../utils.js":23,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var common = require("../common.js"); +var Nav = require("./nav.js"); +var Messages = require("./messages.js"); +var Details = require("./details.js"); + var allTabs = { - request: FlowDetailRequest, - response: FlowDetailResponse, - error: FlowDetailError, - details: FlowDetailConnectionInfo + request: Messages.Request, + response: Messages.Response, + error: Messages.Error, + details: Details }; -var FlowDetail = React.createClass({displayName: "FlowDetail", +var FlowView = React.createClass({displayName: "FlowView", mixins: [common.StickyHeadMixin, common.Navigation, common.State], getTabs: function (flow) { var tabs = []; @@ -1163,7 +1426,7 @@ var FlowDetail = React.createClass({displayName: "FlowDetail", var Tab = allTabs[active]; return ( React.createElement("div", {className: "flow-detail", onScroll: this.adjustHead}, - React.createElement(FlowDetailNav, {ref: "head", + React.createElement(Nav, {ref: "head", flow: flow, tabs: tabs, active: active, @@ -1174,414 +1437,176 @@ var FlowDetail = React.createClass({displayName: "FlowDetail", } }); -module.exports = { - FlowDetail: FlowDetail -}; +module.exports = FlowView; -},{"../actions.js":2,"../flow/utils.js":17,"../utils.js":20,"./common.js":4,"lodash":"lodash","react":"react"}],7:[function(require,module,exports){ +},{"../common.js":4,"./details.js":8,"./messages.js":10,"./nav.js":11,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ var React = require("react"); -var RequestUtils = require("../flow/utils.js").RequestUtils; -var ResponseUtils = require("../flow/utils.js").ResponseUtils; -var utils = require("../utils.js"); - -var TLSColumn = React.createClass({displayName: "TLSColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-tls " + (this.props.className || "") })); - } - }), - sortKeyFun: function(flow){ - return flow.request.scheme; - } - }, - render: function () { - var flow = this.props.flow; - var ssl = (flow.request.scheme == "https"); - var classes; - if (ssl) { - classes = "col-tls col-tls-https"; - } else { - classes = "col-tls col-tls-http"; - } - return React.createElement("td", {className: classes}); - } -}); - - -var IconColumn = React.createClass({displayName: "IconColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-icon " + (this.props.className || "") })); - } - }) - }, - render: function () { - var flow = this.props.flow; - - var icon; - if (flow.response) { - var contentType = ResponseUtils.getContentType(flow.response); - - //TODO: We should assign a type to the flow somewhere else. - if (flow.response.code == 304) { - icon = "resource-icon-not-modified"; - } else if (300 <= flow.response.code && flow.response.code < 400) { - icon = "resource-icon-redirect"; - } else if (contentType && contentType.indexOf("image") >= 0) { - icon = "resource-icon-image"; - } else if (contentType && contentType.indexOf("javascript") >= 0) { - icon = "resource-icon-js"; - } else if (contentType && contentType.indexOf("css") >= 0) { - icon = "resource-icon-css"; - } else if (contentType && contentType.indexOf("html") >= 0) { - icon = "resource-icon-document"; - } - } - if (!icon) { - icon = "resource-icon-plain"; - } - - icon += " resource-icon"; - return React.createElement("td", {className: "col-icon"}, - React.createElement("div", {className: icon}) - ); - } -}); +var flowutils = require("../../flow/utils.js"); +var utils = require("../../utils.js"); -var PathColumn = React.createClass({displayName: "PathColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-path " + (this.props.className || "") }), "Path"); - } - }), - sortKeyFun: function(flow){ - return RequestUtils.pretty_url(flow.request); - } - }, +var Headers = React.createClass({displayName: "Headers", render: function () { - var flow = this.props.flow; - return React.createElement("td", {className: "col-path"}, - flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null, - flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null, - RequestUtils.pretty_url(flow.request) + var rows = this.props.message.headers.map(function (header, i) { + return ( + React.createElement("tr", {key: i}, + React.createElement("td", {className: "header-name"}, header[0] + ":"), + React.createElement("td", {className: "header-value"}, header[1]) + ) + ); + }); + return ( + React.createElement("table", {className: "header-table"}, + React.createElement("tbody", null, + rows + ) + ) ); } }); - -var MethodColumn = React.createClass({displayName: "MethodColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-method " + (this.props.className || "") }), "Method"); - } - }), - sortKeyFun: function(flow){ - return flow.request.method; - } - }, - render: function () { - var flow = this.props.flow; - return React.createElement("td", {className: "col-method"}, flow.request.method); - } -}); - - -var StatusColumn = React.createClass({displayName: "StatusColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-status " + (this.props.className || "") }), "Status"); - } - }), - sortKeyFun: function(flow){ - return flow.response ? flow.response.code : undefined; - } - }, - render: function () { - var flow = this.props.flow; - var status; - if (flow.response) { - status = flow.response.code; - } else { - status = null; - } - return React.createElement("td", {className: "col-status"}, status); - } -}); - - -var SizeColumn = React.createClass({displayName: "SizeColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-size " + (this.props.className || "") }), "Size"); - } - }), - sortKeyFun: function(flow){ - var total = flow.request.contentLength; - if (flow.response) { - total += flow.response.contentLength || 0; - } - return total; - } - }, - render: function () { - var flow = this.props.flow; - - var total = flow.request.contentLength; - if (flow.response) { - total += flow.response.contentLength || 0; - } - var size = utils.formatSize(total); - return React.createElement("td", {className: "col-size"}, size); - } -}); - - -var TimeColumn = React.createClass({displayName: "TimeColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-time " + (this.props.className || "") }), "Time"); - } - }), - sortKeyFun: function(flow){ - if(flow.response) { - return flow.response.timestamp_end - flow.request.timestamp_start; - } - } - }, +var Request = React.createClass({displayName: "Request", render: function () { var flow = this.props.flow; - var time; - if (flow.response) { - time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); + var first_line = [ + flow.request.method, + flowutils.RequestUtils.pretty_url(flow.request), + "HTTP/" + flow.request.httpversion.join(".") + ].join(" "); + var content = null; + if (flow.request.contentLength > 0) { + content = "Request Content Size: " + utils.formatSize(flow.request.contentLength); } else { - time = "..."; - } - return React.createElement("td", {className: "col-time"}, time); - } -}); - - -var all_columns = [ - TLSColumn, - IconColumn, - PathColumn, - MethodColumn, - StatusColumn, - SizeColumn, - TimeColumn -]; - -module.exports = all_columns; - -},{"../flow/utils.js":17,"../utils.js":20,"react":"react"}],8:[function(require,module,exports){ -var React = require("react"); -var common = require("./common.js"); -var utils = require("../utils.js"); -var _ = require("lodash"); + content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + } -var VirtualScrollMixin = require("./virtualscroll.js"); -var flowtable_columns = require("./flowtable-columns.js"); + //TODO: Styling -var FlowRow = React.createClass({displayName: "FlowRow", + return ( + React.createElement("section", null, + React.createElement("div", {className: "first-line"}, first_line ), + React.createElement(Headers, {message: flow.request}), + React.createElement("hr", null), + content + ) + ); + } +}); + +var Response = React.createClass({displayName: "Response", render: function () { var flow = this.props.flow; - var columns = this.props.columns.map(function (Column) { - return React.createElement(Column, {key: Column.displayName, flow: flow}); - }.bind(this)); - var className = ""; - if (this.props.selected) { - className += " selected"; - } - if (this.props.highlighted) { - className += " highlighted"; - } - if (flow.intercepted) { - className += " intercepted"; - } - if (flow.request) { - className += " has-request"; - } - if (flow.response) { - className += " has-response"; + var first_line = [ + "HTTP/" + flow.response.httpversion.join("."), + flow.response.code, + flow.response.msg + ].join(" "); + var content = null; + if (flow.response.contentLength > 0) { + content = "Response Content Size: " + utils.formatSize(flow.response.contentLength); + } else { + content = React.createElement("div", {className: "alert alert-info"}, "No Content"); } + //TODO: Styling + return ( - React.createElement("tr", {className: className, onClick: this.props.selectFlow.bind(null, flow)}, - columns - )); - }, - shouldComponentUpdate: function (nextProps) { - return true; - // Further optimization could be done here - // by calling forceUpdate on flow updates, selection changes and column changes. - //return ( - //(this.props.columns.length !== nextProps.columns.length) || - //(this.props.selected !== nextProps.selected) - //); + React.createElement("section", null, + React.createElement("div", {className: "first-line"}, first_line ), + React.createElement(Headers, {message: flow.response}), + React.createElement("hr", null), + content + ) + ); } }); -var FlowTableHead = React.createClass({displayName: "FlowTableHead", - getInitialState: function(){ - return { - sortColumn: undefined, - sortDesc: false - }; - }, - onClick: function(Column){ - var sortDesc = this.state.sortDesc; - var hasSort = Column.sortKeyFun; - if(Column === this.state.sortColumn){ - sortDesc = !sortDesc; - this.setState({ - sortDesc: sortDesc - }); - } else { - this.setState({ - sortColumn: hasSort && Column, - sortDesc: false - }) - } - var sortKeyFun; - if(!sortDesc){ - sortKeyFun = Column.sortKeyFun; - } else { - sortKeyFun = hasSort && function(){ - var k = Column.sortKeyFun.apply(this, arguments); - if(_.isString(k)){ - return utils.reverseString(""+k); - } else { - return -k; - } - } - } - this.props.setSortKeyFun(sortKeyFun); - }, +var Error = React.createClass({displayName: "Error", render: function () { - var columns = this.props.columns.map(function (Column) { - var onClick = this.onClick.bind(this, Column); - var className; - if(this.state.sortColumn === Column) { - if(this.state.sortDesc){ - className = "sort-desc"; - } else { - className = "sort-asc"; - } - } - return React.createElement(Column.Title, { - key: Column.displayName, - onClick: onClick, - className: className}); - }.bind(this)); - return React.createElement("thead", null, - React.createElement("tr", null, columns) + var flow = this.props.flow; + return ( + React.createElement("section", null, + React.createElement("div", {className: "alert alert-warning"}, + flow.error.msg, + React.createElement("div", null, + React.createElement("small", null, utils.formatTimeStamp(flow.error.timestamp) ) + ) + ) + ) ); } }); +module.exports = { + Request: Request, + Response: Response, + Error: Error +}; + +},{"../../flow/utils.js":20,"../../utils.js":23,"react":"react"}],11:[function(require,module,exports){ +var React = require("react"); -var ROW_HEIGHT = 32; +var actions = require("../../actions.js"); -var FlowTable = React.createClass({displayName: "FlowTable", - mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin], - getInitialState: function () { - return { - columns: flowtable_columns - }; - }, - _listen: function(view){ - if(!view){ - return; - } - view.addListener("add", this.onChange); - view.addListener("update", this.onChange); - view.addListener("remove", this.onChange); - view.addListener("recalculate", this.onChange); - }, - componentWillMount: function () { - this._listen(this.props.view); - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.view !== this.props.view) { - if (this.props.view) { - this.props.view.removeListener("add"); - this.props.view.removeListener("update"); - this.props.view.removeListener("remove"); - this.props.view.removeListener("recalculate"); - } - this._listen(nextProps.view); - } - }, - getDefaultProps: function () { - return { - rowHeight: ROW_HEIGHT - }; - }, - onScrollFlowTable: function () { - this.adjustHead(); - this.onScroll(); - }, - onChange: function () { - this.forceUpdate(); +var NavAction = React.createClass({displayName: "NavAction", + onClick: function (e) { + e.preventDefault(); + this.props.onClick(); }, - scrollIntoView: function (flow) { - this.scrollRowIntoView( - this.props.view.index(flow), - this.refs.body.getDOMNode().offsetTop + render: function () { + return ( + React.createElement("a", {title: this.props.title, + href: "#", + className: "nav-action", + onClick: this.onClick}, + React.createElement("i", {className: "fa fa-fw " + this.props.icon}) + ) ); - }, - renderRow: function (flow) { - var selected = (flow === this.props.selected); - var highlighted = - ( - this.props.view._highlight && - this.props.view._highlight[flow.id] - ); + } +}); - return React.createElement(FlowRow, {key: flow.id, - ref: flow.id, - flow: flow, - columns: this.state.columns, - selected: selected, - highlighted: highlighted, - selectFlow: this.props.selectFlow} - ); - }, +var Nav = React.createClass({displayName: "Nav", render: function () { - //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); - var flows = this.props.view ? this.props.view.list : []; + var flow = this.props.flow; - var rows = this.renderRows(flows); + var tabs = this.props.tabs.map(function (e) { + var str = e.charAt(0).toUpperCase() + e.slice(1); + var className = this.props.active === e ? "active" : ""; + var onClick = function (event) { + this.props.selectTab(e); + event.preventDefault(); + }.bind(this); + return React.createElement("a", {key: e, + href: "#", + className: className, + onClick: onClick}, str); + }.bind(this)); + + var acceptButton = null; + if(flow.intercepted){ + acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)}); + } + var revertButton = null; + if(flow.modified){ + revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)}); + } return ( - React.createElement("div", {className: "flow-table", onScroll: this.onScrollFlowTable}, - React.createElement("table", null, - React.createElement(FlowTableHead, {ref: "head", - columns: this.state.columns, - setSortKeyFun: this.props.setSortKeyFun}), - React.createElement("tbody", {ref: "body"}, - this.getPlaceholderTop(flows.length), - rows, - this.getPlaceholderBottom(flows.length) - ) - ) + React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"}, + tabs, + React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}), + React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}), + React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}), + acceptButton, + revertButton ) ); } }); -module.exports = FlowTable; - +module.exports = Nav; -},{"../utils.js":20,"./common.js":4,"./flowtable-columns.js":7,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +},{"../../actions.js":2,"react":"react"}],12:[function(require,module,exports){ var React = require("react"); var Footer = React.createClass({displayName: "Footer", @@ -1600,7 +1625,7 @@ var Footer = React.createClass({displayName: "Footer", module.exports = Footer; -},{"react":"react"}],10:[function(require,module,exports){ +},{"react":"react"}],13:[function(require,module,exports){ var React = require("react"); var $ = require("jquery"); @@ -1992,7 +2017,7 @@ module.exports = { Header: Header } -},{"../actions.js":2,"../filt/filt.js":16,"../utils.js":20,"./common.js":4,"jquery":"jquery","react":"react"}],11:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":19,"../utils.js":23,"./common.js":4,"jquery":"jquery","react":"react"}],14:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); @@ -2002,7 +2027,7 @@ var toputils = require("../utils.js"); var views = require("../store/view.js"); var Filt = require("../filt/filt.js"); FlowTable = require("./flowtable.js"); -var flowdetail = require("./flowdetail.js"); +var FlowView = require("./flowview/index.js"); var MainView = React.createClass({displayName: "MainView", mixins: [common.Navigation, common.State], @@ -2216,7 +2241,7 @@ var MainView = React.createClass({displayName: "MainView", if (selected) { details = [ React.createElement(common.Splitter, {key: "splitter"}), - React.createElement(flowdetail.FlowDetail, {key: "flowDetails", ref: "flowDetails", flow: selected}) + React.createElement(FlowView, {key: "flowDetails", ref: "flowDetails", flow: selected}) ]; } else { details = null; @@ -2238,7 +2263,7 @@ var MainView = React.createClass({displayName: "MainView", module.exports = MainView; -},{"../actions.js":2,"../filt/filt.js":16,"../store/view.js":19,"../utils.js":20,"./common.js":4,"./flowdetail.js":6,"./flowtable.js":8,"react":"react"}],12:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":19,"../store/view.js":22,"../utils.js":23,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":9,"react":"react"}],15:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -2333,7 +2358,7 @@ module.exports = { routes: routes }; -},{"../actions.js":2,"../store/store.js":18,"./common.js":4,"./eventlog.js":5,"./footer.js":9,"./header.js":10,"./mainview.js":11,"lodash":"lodash","react":"react","react-router":"react-router"}],13:[function(require,module,exports){ +},{"../actions.js":2,"../store/store.js":21,"./common.js":4,"./eventlog.js":5,"./footer.js":12,"./header.js":13,"./mainview.js":14,"lodash":"lodash","react":"react","react-router":"react-router"}],16:[function(require,module,exports){ var React = require("react"); var VirtualScrollMixin = { @@ -2420,7 +2445,7 @@ var VirtualScrollMixin = { module.exports = VirtualScrollMixin; -},{"react":"react"}],14:[function(require,module,exports){ +},{"react":"react"}],17:[function(require,module,exports){ var actions = require("./actions.js"); @@ -2450,7 +2475,7 @@ function Connection(url) { module.exports = Connection; -},{"./actions.js":2}],15:[function(require,module,exports){ +},{"./actions.js":2}],18:[function(require,module,exports){ var flux = require("flux"); @@ -2474,7 +2499,7 @@ module.exports = { AppDispatcher: AppDispatcher }; -},{"flux":"flux"}],16:[function(require,module,exports){ +},{"flux":"flux"}],19:[function(require,module,exports){ module.exports = (function() { /* * Generated by PEG.js 0.8.0. @@ -4250,10 +4275,10 @@ module.exports = (function() { }; })(); -},{"../flow/utils.js":17}],17:[function(require,module,exports){ +},{"../flow/utils.js":20}],20:[function(require,module,exports){ var _ = require("lodash"); -var _MessageUtils = { +var MessageUtils = { getContentType: function (message) { return this.get_first_header(message, /^Content-Type$/i); }, @@ -4295,7 +4320,7 @@ var defaultPorts = { "https": 443 }; -var RequestUtils = _.extend(_MessageUtils, { +var RequestUtils = _.extend(MessageUtils, { pretty_host: function (request) { //FIXME: Add hostheader return request.host; @@ -4309,16 +4334,16 @@ var RequestUtils = _.extend(_MessageUtils, { } }); -var ResponseUtils = _.extend(_MessageUtils, {}); +var ResponseUtils = _.extend(MessageUtils, {}); module.exports = { ResponseUtils: ResponseUtils, - RequestUtils: RequestUtils - -} + RequestUtils: RequestUtils, + MessageUtils: MessageUtils +}; -},{"lodash":"lodash"}],18:[function(require,module,exports){ +},{"lodash":"lodash"}],21:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); @@ -4501,7 +4526,7 @@ module.exports = { FlowStore: FlowStore }; -},{"../actions.js":2,"../dispatcher.js":15,"../utils.js":20,"events":1,"jquery":"jquery","lodash":"lodash"}],19:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":18,"../utils.js":23,"events":1,"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ var EventEmitter = require('events').EventEmitter; var _ = require("lodash"); @@ -4617,7 +4642,7 @@ module.exports = { StoreView: StoreView }; -},{"../utils.js":20,"events":1,"lodash":"lodash"}],20:[function(require,module,exports){ +},{"../utils.js":23,"events":1,"lodash":"lodash"}],23:[function(require,module,exports){ var $ = require("jquery"); var _ = require("lodash"); -- cgit v1.2.3 From 1143552e1690f8b96b3d95381f7f06cbb46ead59 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 00:21:38 +0100 Subject: web: add content views --- libmproxy/web/static/app.js | 215 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 182 insertions(+), 33 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 04d6f282..eb8ef45e 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -443,7 +443,7 @@ $(function () { -},{"./components/proxyapp.js":15,"./connection":17,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ +},{"./components/proxyapp.js":16,"./connection":18,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -777,7 +777,7 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../actions.js":2,"../store/view.js":22,"./common.js":4,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":23,"./common.js":4,"./virtualscroll.js":17,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); var RequestUtils = require("../flow/utils.js").RequestUtils; var ResponseUtils = require("../flow/utils.js").ResponseUtils; @@ -980,7 +980,7 @@ var all_columns = [ module.exports = all_columns; -},{"../flow/utils.js":20,"../utils.js":23,"react":"react"}],7:[function(require,module,exports){ +},{"../flow/utils.js":21,"../utils.js":24,"react":"react"}],7:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); var utils = require("../utils.js"); @@ -1180,7 +1180,167 @@ var FlowTable = React.createClass({displayName: "FlowTable", module.exports = FlowTable; -},{"../utils.js":23,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +},{"../utils.js":24,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":17,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var MessageUtils = require("../../flow/utils.js").MessageUtils; +var utils = require("../../utils.js"); + +var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; +var Image = React.createClass({displayName: "Image", + statics: { + matches: function (message) { + return image_regex.test(MessageUtils.getContentType(message)); + } + }, + render: function () { + var message_name = this.props.flow.request === this.props.message ? "request" : "response"; + var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content"; + return React.createElement("div", {className: "flowview-image"}, + React.createElement("img", {src: url, alt: "preview", className: "img-thumbnail"}) + ); + } +}); + +var Raw = React.createClass({displayName: "Raw", + statics: { + matches: function (message) { + return true; + } + }, + render: function () { + //FIXME + return React.createElement("div", null, "raw"); + } +}); + + +var Auto = React.createClass({displayName: "Auto", + statics: { + matches: function () { + return false; // don't match itself + }, + findView: function (message) { + for (var i = 0; i < all.length; i++) { + if (all[i].matches(message)) { + return all[i]; + } + } + return all[all.length - 1]; + } + }, + render: function () { + var View = Auto.findView(this.props.message); + return React.createElement(View, React.__spread({}, this.props)); + } +}); + +var all = [Auto, Image, Raw]; + + +var ContentEmpty = React.createClass({displayName: "ContentEmpty", + render: function () { + var message_name = this.props.flow.request === this.props.message ? "request" : "response"; + return React.createElement("div", {className: "alert alert-info"}, "No ", message_name, " content."); + } +}); + +var ContentMissing = React.createClass({displayName: "ContentMissing", + render: function () { + var message_name = this.props.flow.request === this.props.message ? "Request" : "Response"; + return React.createElement("div", {className: "alert alert-info"}, message_name, " content missing."); + } +}); + +var TooLarge = React.createClass({displayName: "TooLarge", + render: function () { + var size = utils.formatSize(this.props.message.contentLength); + return React.createElement("div", {className: "alert alert-warning"}, + React.createElement("button", {onClick: this.props.onClick, className: "btn btn-xs btn-warning pull-right"}, "Display anyway"), + size, " content size." + ); + } +}); + +var ViewSelector = React.createClass({displayName: "ViewSelector", + render: function () { + var views = []; + for (var i = 0; i < all.length; i++) { + var view = all[i]; + var className = "btn btn-default"; + if (view === this.props.active) { + className += " active"; + } + var text; + if (view === Auto) { + text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase(); + } else { + text = view.displayName.toLowerCase(); + } + views.push( + React.createElement("button", { + key: view.displayName, + onClick: this.props.selectView.bind(null, view), + className: className}, + text + ) + ); + } + + return React.createElement("div", {className: "view-selector btn-group btn-group-xs"}, views); + } +}); + +var ContentView = React.createClass({displayName: "ContentView", + getInitialState: function () { + return { + displayLarge: false, + View: Auto + }; + }, + propTypes: { + // It may seem a bit weird at the first glance: + // Every view takes the flow and the message as props, e.g. + // + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + }, + selectView: function (view) { + this.setState({ + View: view + }); + }, + displayLarge: function () { + this.setState({displayLarge: true}); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.setState(this.getInitialState()); + } + }, + render: function () { + var message = this.props.message; + if (message.contentLength === 0) { + return React.createElement(ContentEmpty, React.__spread({}, this.props)); + } else if (message.contentLength === null) { + return React.createElement(ContentMissing, React.__spread({}, this.props)); + } else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) { + return React.createElement(TooLarge, React.__spread({}, this.props, {onClick: this.displayLarge})); + } + + return React.createElement("div", null, + React.createElement(this.state.View, React.__spread({}, this.props)), + React.createElement("div", {className: "text-center"}, + React.createElement(ViewSelector, {selectView: this.selectView, active: this.state.View, message: message}) + ) + ); + } +}); + +module.exports = ContentView; + +},{"../../flow/utils.js":21,"../../utils.js":24,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ var React = require("react"); var _ = require("lodash"); @@ -1363,7 +1523,7 @@ var Details = React.createClass({displayName: "Details", module.exports = Details; -},{"../../utils.js":23,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +},{"../../utils.js":24,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ var React = require("react"); var _ = require("lodash"); @@ -1439,11 +1599,12 @@ var FlowView = React.createClass({displayName: "FlowView", module.exports = FlowView; -},{"../common.js":4,"./details.js":8,"./messages.js":10,"./nav.js":11,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ +},{"../common.js":4,"./details.js":9,"./messages.js":11,"./nav.js":12,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){ var React = require("react"); var flowutils = require("../../flow/utils.js"); var utils = require("../../utils.js"); +var ContentView = require("./contentview.js"); var Headers = React.createClass({displayName: "Headers", render: function () { @@ -1473,12 +1634,6 @@ var Request = React.createClass({displayName: "Request", flowutils.RequestUtils.pretty_url(flow.request), "HTTP/" + flow.request.httpversion.join(".") ].join(" "); - var content = null; - if (flow.request.contentLength > 0) { - content = "Request Content Size: " + utils.formatSize(flow.request.contentLength); - } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); - } //TODO: Styling @@ -1487,7 +1642,7 @@ var Request = React.createClass({displayName: "Request", React.createElement("div", {className: "first-line"}, first_line ), React.createElement(Headers, {message: flow.request}), React.createElement("hr", null), - content + React.createElement(ContentView, {flow: flow, message: flow.request}) ) ); } @@ -1501,12 +1656,6 @@ var Response = React.createClass({displayName: "Response", flow.response.code, flow.response.msg ].join(" "); - var content = null; - if (flow.response.contentLength > 0) { - content = "Response Content Size: " + utils.formatSize(flow.response.contentLength); - } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); - } //TODO: Styling @@ -1515,7 +1664,7 @@ var Response = React.createClass({displayName: "Response", React.createElement("div", {className: "first-line"}, first_line ), React.createElement(Headers, {message: flow.response}), React.createElement("hr", null), - content + React.createElement(ContentView, {flow: flow, message: flow.response}) ) ); } @@ -1543,7 +1692,7 @@ module.exports = { Error: Error }; -},{"../../flow/utils.js":20,"../../utils.js":23,"react":"react"}],11:[function(require,module,exports){ +},{"../../flow/utils.js":21,"../../utils.js":24,"./contentview.js":8,"react":"react"}],12:[function(require,module,exports){ var React = require("react"); var actions = require("../../actions.js"); @@ -1606,7 +1755,7 @@ var Nav = React.createClass({displayName: "Nav", module.exports = Nav; -},{"../../actions.js":2,"react":"react"}],12:[function(require,module,exports){ +},{"../../actions.js":2,"react":"react"}],13:[function(require,module,exports){ var React = require("react"); var Footer = React.createClass({displayName: "Footer", @@ -1625,7 +1774,7 @@ var Footer = React.createClass({displayName: "Footer", module.exports = Footer; -},{"react":"react"}],13:[function(require,module,exports){ +},{"react":"react"}],14:[function(require,module,exports){ var React = require("react"); var $ = require("jquery"); @@ -2017,7 +2166,7 @@ module.exports = { Header: Header } -},{"../actions.js":2,"../filt/filt.js":19,"../utils.js":23,"./common.js":4,"jquery":"jquery","react":"react"}],14:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":20,"../utils.js":24,"./common.js":4,"jquery":"jquery","react":"react"}],15:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); @@ -2263,7 +2412,7 @@ var MainView = React.createClass({displayName: "MainView", module.exports = MainView; -},{"../actions.js":2,"../filt/filt.js":19,"../store/view.js":22,"../utils.js":23,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":9,"react":"react"}],15:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":20,"../store/view.js":23,"../utils.js":24,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":10,"react":"react"}],16:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -2358,7 +2507,7 @@ module.exports = { routes: routes }; -},{"../actions.js":2,"../store/store.js":21,"./common.js":4,"./eventlog.js":5,"./footer.js":12,"./header.js":13,"./mainview.js":14,"lodash":"lodash","react":"react","react-router":"react-router"}],16:[function(require,module,exports){ +},{"../actions.js":2,"../store/store.js":22,"./common.js":4,"./eventlog.js":5,"./footer.js":13,"./header.js":14,"./mainview.js":15,"lodash":"lodash","react":"react","react-router":"react-router"}],17:[function(require,module,exports){ var React = require("react"); var VirtualScrollMixin = { @@ -2445,7 +2594,7 @@ var VirtualScrollMixin = { module.exports = VirtualScrollMixin; -},{"react":"react"}],17:[function(require,module,exports){ +},{"react":"react"}],18:[function(require,module,exports){ var actions = require("./actions.js"); @@ -2475,7 +2624,7 @@ function Connection(url) { module.exports = Connection; -},{"./actions.js":2}],18:[function(require,module,exports){ +},{"./actions.js":2}],19:[function(require,module,exports){ var flux = require("flux"); @@ -2499,7 +2648,7 @@ module.exports = { AppDispatcher: AppDispatcher }; -},{"flux":"flux"}],19:[function(require,module,exports){ +},{"flux":"flux"}],20:[function(require,module,exports){ module.exports = (function() { /* * Generated by PEG.js 0.8.0. @@ -4275,7 +4424,7 @@ module.exports = (function() { }; })(); -},{"../flow/utils.js":20}],20:[function(require,module,exports){ +},{"../flow/utils.js":21}],21:[function(require,module,exports){ var _ = require("lodash"); var MessageUtils = { @@ -4343,7 +4492,7 @@ module.exports = { MessageUtils: MessageUtils }; -},{"lodash":"lodash"}],21:[function(require,module,exports){ +},{"lodash":"lodash"}],22:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); @@ -4526,7 +4675,7 @@ module.exports = { FlowStore: FlowStore }; -},{"../actions.js":2,"../dispatcher.js":18,"../utils.js":23,"events":1,"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":19,"../utils.js":24,"events":1,"jquery":"jquery","lodash":"lodash"}],23:[function(require,module,exports){ var EventEmitter = require('events').EventEmitter; var _ = require("lodash"); @@ -4642,7 +4791,7 @@ module.exports = { StoreView: StoreView }; -},{"../utils.js":23,"events":1,"lodash":"lodash"}],23:[function(require,module,exports){ +},{"../utils.js":24,"events":1,"lodash":"lodash"}],24:[function(require,module,exports){ var $ = require("jquery"); var _ = require("lodash"); -- cgit v1.2.3 From 941584623281905fec22d8857c5501d196c051f7 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 02:25:47 +0100 Subject: web: raw content view --- libmproxy/web/static/app.js | 58 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index eb8ef45e..2254b415 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -1195,23 +1195,56 @@ var Image = React.createClass({displayName: "Image", } }, render: function () { - var message_name = this.props.flow.request === this.props.message ? "request" : "response"; - var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content"; + var url = MessageUtils.getContentURL(this.props.flow, this.props.message); return React.createElement("div", {className: "flowview-image"}, React.createElement("img", {src: url, alt: "preview", className: "img-thumbnail"}) ); } }); +var RawMixin = { + getInitialState: function () { + return { + content: undefined + } + }, + requestContent: function (nextProps) { + this.setState({content: undefined}); + var request = MessageUtils.getContent(nextProps.flow, nextProps.message); + request.done(function (data) { + this.setState({content: data}); + }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { + this.setState({content: "AJAX Error: " + textStatus}); + }.bind(this)); + + }, + componentWillMount: function () { + this.requestContent(this.props); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.requestContent(nextProps); + } + }, + render: function () { + if (!this.state.content) { + return React.createElement("div", {className: "text-center"}, + React.createElement("i", {className: "fa fa-spinner fa-spin"}) + ); + } + return this.renderContent(); + } +}; + var Raw = React.createClass({displayName: "Raw", + mixins: [RawMixin], statics: { matches: function (message) { return true; } }, - render: function () { - //FIXME - return React.createElement("div", null, "raw"); + renderContent: function () { + return React.createElement("pre", null, this.state.content); } }); @@ -4426,6 +4459,7 @@ module.exports = (function() { },{"../flow/utils.js":21}],21:[function(require,module,exports){ var _ = require("lodash"); +var $ = require("jquery"); var MessageUtils = { getContentType: function (message) { @@ -4461,6 +4495,18 @@ var MessageUtils = { } } return false; + }, + getContentURL: function(flow, message){ + if(message === flow.request){ + message = "request"; + } else if (message === flow.response){ + message = "response"; + } + return "/flows/" + flow.id + "/" + message + "/content"; + }, + getContent: function(flow, message){ + var url = MessageUtils.getContentURL(flow, message); + return $.get(url); } }; @@ -4492,7 +4538,7 @@ module.exports = { MessageUtils: MessageUtils }; -},{"lodash":"lodash"}],22:[function(require,module,exports){ +},{"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); -- cgit v1.2.3 From cf9f91b0b4abe2020c544981d6dc2e2e85f4b4bd Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 14:33:42 +0100 Subject: web: upgrade to react 0.13 --- libmproxy/web/static/app.js | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 2254b415..4f3998a9 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -478,31 +478,43 @@ var StickyHeadMixin = { var Navigation = _.extend({}, ReactRouter.Navigation, { setQuery: function (dict) { - var q = this.context.getCurrentQuery(); + var q = this.context.router.getCurrentQuery(); for(var i in dict){ if(dict.hasOwnProperty(i)){ q[i] = dict[i] || undefined; //falsey values shall be removed. } } q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/957 - this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); + this.replaceWith(this.context.router.getCurrentPath(), this.context.router.getCurrentParams(), q); }, replaceWith: function(routeNameOrPath, params, query) { if(routeNameOrPath === undefined){ - routeNameOrPath = this.context.getCurrentPath(); + routeNameOrPath = this.context.router.getCurrentPath(); } if(params === undefined){ - params = this.context.getCurrentParams(); + params = this.context.router.getCurrentParams(); } if(query === undefined) { - query = this.context.getCurrentQuery(); + query = this.context.router.getCurrentQuery(); } - // FIXME: react-router is just broken. - ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); + // FIXME: react-router is just broken, + // we hopefully just need to wait for the next release with https://github.com/rackt/react-router/pull/957. + this.context.router.replaceWith(routeNameOrPath, params, query); + } +}); + +// react-router is fairly good at changing its API regularly. +// We keep the old method for now - if it should turn out that their changes are permanent, +// we may remove this mixin and access react-router directly again. +var State = _.extend({}, ReactRouter.State, { + getQuery: function(){ + return this.context.router.getCurrentQuery(); + }, + getParams: function(){ + return this.context.router.getCurrentParams(); } }); -_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); var Splitter = React.createClass({displayName: "Splitter", getDefaultProps: function () { @@ -610,7 +622,7 @@ var Splitter = React.createClass({displayName: "Splitter", }); module.exports = { - State: ReactRouter.State, // keep here - react-router is pretty buggy, we may need workarounds in the future. + State: State, Navigation: Navigation, StickyHeadMixin: StickyHeadMixin, AutoScrollMixin: AutoScrollMixin, @@ -2166,15 +2178,17 @@ var Header = React.createClass({displayName: "Header", }, render: function () { var header = header_entries.map(function (entry, i) { - var classes = React.addons.classSet({ - active: entry == this.state.active - }); + var className; + if(entry === this.state.active){ + className = "active"; + } else { + className = ""; + } return ( React.createElement("a", {key: i, href: "#", - className: classes, - onClick: this.handleClick.bind(this, entry) - }, + className: className, + onClick: this.handleClick.bind(this, entry)}, entry.title ) ); -- cgit v1.2.3 From 39a8ac7e2a405b84edc55289cb8bdfb2ac1948fa Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 15:19:35 +0100 Subject: web: improve views --- libmproxy/web/static/app.js | 96 +++++++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 25 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 4f3998a9..84681403 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -303,6 +303,7 @@ function isUndefined(arg) { },{}],2:[function(require,module,exports){ var $ = require("jquery"); +var AppDispatcher = require("./dispatcher.js").AppDispatcher; var ActionTypes = { // Connection @@ -422,10 +423,11 @@ module.exports = { FlowActions: FlowActions, StoreCmds: StoreCmds, SettingsActions: SettingsActions, + EventLogActions: EventLogActions, Query: Query }; -},{"jquery":"jquery"}],3:[function(require,module,exports){ +},{"./dispatcher.js":19,"jquery":"jquery"}],3:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); @@ -1200,7 +1202,7 @@ var MessageUtils = require("../../flow/utils.js").MessageUtils; var utils = require("../../utils.js"); var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; -var Image = React.createClass({displayName: "Image", +var ViewImage = React.createClass({displayName: "ViewImage", statics: { matches: function (message) { return image_regex.test(MessageUtils.getContentType(message)); @@ -1217,16 +1219,28 @@ var Image = React.createClass({displayName: "Image", var RawMixin = { getInitialState: function () { return { - content: undefined + content: undefined, + request: undefined } }, requestContent: function (nextProps) { - this.setState({content: undefined}); + if(this.state.request){ + this.state.request.abort(); + } var request = MessageUtils.getContent(nextProps.flow, nextProps.message); + this.setState({ + content: undefined, + request: request + }); request.done(function (data) { this.setState({content: data}); }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { - this.setState({content: "AJAX Error: " + textStatus}); + if(textStatus === "abort"){ + return; + } + this.setState({content: "AJAX Error: " + textStatus + "\r\n" + errorThrown}); + }.bind(this)).always(function(){ + this.setState({request: undefined}); }.bind(this)); }, @@ -1238,6 +1252,11 @@ var RawMixin = { this.requestContent(nextProps); } }, + componentWillUnmount: function(){ + if(this.state.request){ + this.state.request.abort(); + } + }, render: function () { if (!this.state.content) { return React.createElement("div", {className: "text-center"}, @@ -1248,7 +1267,7 @@ var RawMixin = { } }; -var Raw = React.createClass({displayName: "Raw", +var ViewRaw = React.createClass({displayName: "ViewRaw", mixins: [RawMixin], statics: { matches: function (message) { @@ -1260,8 +1279,25 @@ var Raw = React.createClass({displayName: "Raw", } }); +var json_regex = /^application\/json$/i; +var ViewJSON = React.createClass({displayName: "ViewJSON", + mixins: [RawMixin], + statics: { + matches: function (message) { + return json_regex.test(MessageUtils.getContentType(message)); + } + }, + renderContent: function () { + var json = this.state.content; + try { + json = JSON.stringify(JSON.parse(json), null, 2); + } catch(e) { + } + return React.createElement("pre", null, json); + } +}); -var Auto = React.createClass({displayName: "Auto", +var ViewAuto = React.createClass({displayName: "ViewAuto", statics: { matches: function () { return false; // don't match itself @@ -1276,12 +1312,12 @@ var Auto = React.createClass({displayName: "Auto", } }, render: function () { - var View = Auto.findView(this.props.message); + var View = ViewAuto.findView(this.props.message); return React.createElement(View, React.__spread({}, this.props)); } }); -var all = [Auto, Image, Raw]; +var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw]; var ContentEmpty = React.createClass({displayName: "ContentEmpty", @@ -1299,6 +1335,12 @@ var ContentMissing = React.createClass({displayName: "ContentMissing", }); var TooLarge = React.createClass({displayName: "TooLarge", + statics: { + isTooLarge: function(message){ + var max_mb = ViewImage.matches(message) ? 10 : 0.2; + return message.contentLength > 1024 * 1024 * max_mb; + } + }, render: function () { var size = utils.formatSize(this.props.message.contentLength); return React.createElement("div", {className: "alert alert-warning"}, @@ -1318,10 +1360,10 @@ var ViewSelector = React.createClass({displayName: "ViewSelector", className += " active"; } var text; - if (view === Auto) { - text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase(); + if (view === ViewAuto) { + text = "auto: " + ViewAuto.findView(this.props.message).displayName.toLowerCase().replace("view", ""); } else { - text = view.displayName.toLowerCase(); + text = view.displayName.toLowerCase().replace("view", ""); } views.push( React.createElement("button", { @@ -1341,7 +1383,7 @@ var ContentView = React.createClass({displayName: "ContentView", getInitialState: function () { return { displayLarge: false, - View: Auto + View: ViewAuto }; }, propTypes: { @@ -1370,7 +1412,7 @@ var ContentView = React.createClass({displayName: "ContentView", return React.createElement(ContentEmpty, React.__spread({}, this.props)); } else if (message.contentLength === null) { return React.createElement(ContentMissing, React.__spread({}, this.props)); - } else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) { + } else if (!this.state.displayLarge && TooLarge.isTooLarge(message)) { return React.createElement(TooLarge, React.__spread({}, this.props, {onClick: this.displayLarge})); } @@ -2644,6 +2686,7 @@ module.exports = VirtualScrollMixin; },{"react":"react"}],18:[function(require,module,exports){ var actions = require("./actions.js"); +var AppDispatcher = require("./dispatcher.js").AppDispatcher; function Connection(url) { if (url[0] === "/") { @@ -2660,18 +2703,18 @@ function Connection(url) { }; ws.onerror = function () { actions.ConnectionActions.error(); - EventLogActions.add_event("WebSocket connection error."); + actions.EventLogActions.add_event("WebSocket connection error."); }; ws.onclose = function () { actions.ConnectionActions.close(); - EventLogActions.add_event("WebSocket connection closed."); + actions.EventLogActions.add_event("WebSocket connection closed."); }; return ws; } module.exports = Connection; -},{"./actions.js":2}],19:[function(require,module,exports){ +},{"./actions.js":2,"./dispatcher.js":19}],19:[function(require,module,exports){ var flux = require("flux"); @@ -2681,7 +2724,7 @@ const PayloadSources = { }; -AppDispatcher = new flux.Dispatcher(); +var AppDispatcher = new flux.Dispatcher(); AppDispatcher.dispatchViewAction = function (action) { action.source = PayloadSources.VIEW; this.dispatch(action); @@ -4477,7 +4520,7 @@ var $ = require("jquery"); var MessageUtils = { getContentType: function (message) { - return this.get_first_header(message, /^Content-Type$/i); + return this.get_first_header(message, /^Content-Type$/i).split(";")[0].trim(); }, get_first_header: function (message, regex) { //FIXME: Cache Invalidation. @@ -4854,6 +4897,7 @@ module.exports = { },{"../utils.js":24,"events":1,"lodash":"lodash"}],24:[function(require,module,exports){ var $ = require("jquery"); var _ = require("lodash"); +var actions = require("./actions.js"); var Key = { UP: 38, @@ -4921,13 +4965,13 @@ var end = String.fromCharCode(0xffff); function reverseString(s){ return String.fromCharCode.apply(String, _.map(s.split(""), function (c) { - return 0xffff - c.charCodeAt(); + return 0xffff - c.charCodeAt(0); }) ) + end; } function getCookie(name) { - var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); + var r = document.cookie.match(new RegExp("\\b" + name + "=([^;]*)\\b")); return r ? r[1] : undefined; } var xsrf = $.param({_xsrf: getCookie("_xsrf")}); @@ -4944,10 +4988,12 @@ $.ajaxPrefilter(function (options) { }); // Log AJAX Errors $(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) { + if(thrownError === "abort"){ + return; + } var message = jqXHR.responseText; - console.error(message, arguments); - EventLogActions.add_event(thrownError + ": " + message); - window.alert(message); + console.error(thrownError, message, arguments); + actions.EventLogActions.add_event(thrownError + ": " + message); }); module.exports = { @@ -4958,7 +5004,7 @@ module.exports = { Key: Key }; -},{"jquery":"jquery","lodash":"lodash"}]},{},[3]) +},{"./actions.js":2,"jquery":"jquery","lodash":"lodash"}]},{},[3]) //# sourceMappingURL=app.js.map \ No newline at end of file -- cgit v1.2.3 From 2acd77dea025b489d5d0ca19fc1c84901ac335d4 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 15:29:55 +0100 Subject: web: add content download --- libmproxy/web/static/app.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 84681403..83bb99ab 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -1416,10 +1416,14 @@ var ContentView = React.createClass({displayName: "ContentView", return React.createElement(TooLarge, React.__spread({}, this.props, {onClick: this.displayLarge})); } + var downloadUrl = MessageUtils.getContentURL(this.props.flow, message); + return React.createElement("div", null, React.createElement(this.state.View, React.__spread({}, this.props)), - React.createElement("div", {className: "text-center"}, - React.createElement(ViewSelector, {selectView: this.selectView, active: this.state.View, message: message}) + React.createElement("div", {className: "view-options text-center"}, + React.createElement(ViewSelector, {selectView: this.selectView, active: this.state.View, message: message}), + " ", + React.createElement("a", {className: "btn btn-default btn-xs", href: downloadUrl}, React.createElement("i", {className: "fa fa-download"})) ) ); } -- cgit v1.2.3 From 968c7021dfef00c459899520921faf7367e923d9 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Mon, 23 Mar 2015 00:24:56 +0100 Subject: web: add basic edit capability for first line --- libmproxy/web/static/app.js | 401 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 339 insertions(+), 62 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 83bb99ab..e972e298 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -303,6 +303,7 @@ function isUndefined(arg) { },{}],2:[function(require,module,exports){ var $ = require("jquery"); +var _ = require("lodash"); var AppDispatcher = require("./dispatcher.js").AppDispatcher; var ActionTypes = { @@ -348,7 +349,8 @@ var SettingsActions = { $.ajax({ type: "PUT", url: "/settings", - data: settings + contentType: 'application/json', + data: JSON.stringify(settings) }); /* @@ -399,15 +401,26 @@ var FlowActions = { revert: function(flow){ $.post("/flows/" + flow.id + "/revert"); }, - update: function (flow) { + update: function (flow, nextProps) { + /* + //Facebook Flux: We do an optimistic update on the client already. + var nextFlow = _.cloneDeep(flow); + _.merge(nextFlow, nextProps); AppDispatcher.dispatchViewAction({ type: ActionTypes.FLOW_STORE, cmd: StoreCmds.UPDATE, - data: flow + data: nextFlow + }); + */ + $.ajax({ + type: "PUT", + url: "/flows/" + flow.id, + contentType: 'application/json', + data: JSON.stringify(nextProps) }); }, clear: function(){ - $.post("/clear"); + $.post("/flows/" + flow.id); } }; @@ -427,7 +440,7 @@ module.exports = { Query: Query }; -},{"./dispatcher.js":19,"jquery":"jquery"}],3:[function(require,module,exports){ +},{"./dispatcher.js":19,"jquery":"jquery","lodash":"lodash"}],3:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); @@ -478,6 +491,13 @@ var StickyHeadMixin = { }; +var ChildFocus = { + contextTypes: { + returnFocus: React.PropTypes.func + } +}; + + var Navigation = _.extend({}, ReactRouter.Navigation, { setQuery: function (dict) { var q = this.context.router.getCurrentQuery(); @@ -624,6 +644,7 @@ var Splitter = React.createClass({displayName: "Splitter", }); module.exports = { + ChildFocus: ChildFocus, State: State, Navigation: Navigation, StickyHeadMixin: StickyHeadMixin, @@ -810,7 +831,7 @@ var TLSColumn = React.createClass({displayName: "TLSColumn", }, render: function () { var flow = this.props.flow; - var ssl = (flow.request.scheme == "https"); + var ssl = (flow.request.scheme === "https"); var classes; if (ssl) { classes = "col-tls col-tls-https"; @@ -838,7 +859,7 @@ var IconColumn = React.createClass({displayName: "IconColumn", var contentType = ResponseUtils.getContentType(flow.response); //TODO: We should assign a type to the flow somewhere else. - if (flow.response.code == 304) { + if (flow.response.code === 304) { icon = "resource-icon-not-modified"; } else if (300 <= flow.response.code && flow.response.code < 400) { icon = "resource-icon-redirect"; @@ -1224,7 +1245,7 @@ var RawMixin = { } }, requestContent: function (nextProps) { - if(this.state.request){ + if (this.state.request) { this.state.request.abort(); } var request = MessageUtils.getContent(nextProps.flow, nextProps.message); @@ -1235,11 +1256,11 @@ var RawMixin = { request.done(function (data) { this.setState({content: data}); }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { - if(textStatus === "abort"){ + if (textStatus === "abort") { return; } this.setState({content: "AJAX Error: " + textStatus + "\r\n" + errorThrown}); - }.bind(this)).always(function(){ + }.bind(this)).always(function () { this.setState({request: undefined}); }.bind(this)); @@ -1252,8 +1273,8 @@ var RawMixin = { this.requestContent(nextProps); } }, - componentWillUnmount: function(){ - if(this.state.request){ + componentWillUnmount: function () { + if (this.state.request) { this.state.request.abort(); } }, @@ -1291,7 +1312,7 @@ var ViewJSON = React.createClass({displayName: "ViewJSON", var json = this.state.content; try { json = JSON.stringify(JSON.parse(json), null, 2); - } catch(e) { + } catch (e) { } return React.createElement("pre", null, json); } @@ -1336,7 +1357,7 @@ var ContentMissing = React.createClass({displayName: "ContentMissing", var TooLarge = React.createClass({displayName: "TooLarge", statics: { - isTooLarge: function(message){ + isTooLarge: function (message) { var max_mb = ViewImage.matches(message) ? 10 : 0.2; return message.contentLength > 1024 * 1024 * max_mb; } @@ -1422,8 +1443,10 @@ var ContentView = React.createClass({displayName: "ContentView", React.createElement(this.state.View, React.__spread({}, this.props)), React.createElement("div", {className: "view-options text-center"}, React.createElement(ViewSelector, {selectView: this.selectView, active: this.state.View, message: message}), - " ", - React.createElement("a", {className: "btn btn-default btn-xs", href: downloadUrl}, React.createElement("i", {className: "fa fa-download"})) + " ", + React.createElement("a", {className: "btn btn-default btn-xs", href: downloadUrl}, + React.createElement("i", {className: "fa fa-download"}) + ) ) ); } @@ -1692,7 +1715,10 @@ module.exports = FlowView; },{"../common.js":4,"./details.js":9,"./messages.js":11,"./nav.js":12,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){ var React = require("react"); +var _ = require("lodash"); +var common = require("../common.js"); +var actions = require("../../actions.js"); var flowutils = require("../../flow/utils.js"); var utils = require("../../utils.js"); var ContentView = require("./contentview.js"); @@ -1717,20 +1743,217 @@ var Headers = React.createClass({displayName: "Headers", } }); -var Request = React.createClass({displayName: "Request", +var InlineInput = React.createClass({displayName: "InlineInput", + mixins: [common.ChildFocus], + getInitialState: function () { + return { + editable: false + }; + }, + render: function () { + var Tag = this.props.tag || "span"; + var className = "inline-input " + (this.props.className || ""); + var html = {__html: _.escape(this.props.content)}; + return React.createElement(Tag, React.__spread({}, + this.props, + {tabIndex: "0", + className: className, + contentEditable: this.state.editable || undefined, + onInput: this.onInput, + onFocus: this.onFocus, + onBlur: this.onBlur, + onKeyDown: this.onKeyDown, + dangerouslySetInnerHTML: html}) + ); + }, + onKeyDown: function (e) { + e.stopPropagation(); + switch (e.keyCode) { + case utils.Key.ESC: + this.blur(); + break; + case utils.Key.ENTER: + e.preventDefault(); + if (!e.ctrlKey) { + this.blur(); + } else { + this.props.onDone && this.props.onDone(); + } + break; + default: + break; + } + }, + blur: function(){ + this.getDOMNode().blur(); + this.context.returnFocus && this.context.returnFocus(); + }, + selectContents: function () { + var range = document.createRange(); + range.selectNodeContents(this.getDOMNode()); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + }, + onFocus: function () { + this.setState({editable: true}, this.selectContents); + }, + onBlur: function (e) { + this.setState({editable: false}); + this.handleChange(); + this.props.onDone && this.props.onDone(); + }, + onInput: function () { + this.handleChange(); + }, + handleChange: function () { + var content = this.getDOMNode().textContent; + if (content !== this.props.content) { + this.props.onChange(content); + } + } +}); + +var ValidateInlineInput = React.createClass({displayName: "ValidateInlineInput", + getInitialState: function () { + return { + content: ""+this.props.content, + originalContent: ""+this.props.content + }; + }, + onChange: function (val) { + this.setState({ + content: val + }); + }, + onDone: function () { + if (this.state.content === this.state.originalContent) { + return true; + } + if (this.props.isValid(this.state.content)) { + this.props.onChange(this.state.content); + } else { + this.setState({ + content: this.state.originalContent + }); + } + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.content !== this.state.content) { + this.setState({ + content: ""+nextProps.content, + originalContent: ""+nextProps.content + }) + } + }, + render: function () { + var className = this.props.className || ""; + if (this.props.isValid(this.state.content)) { + className += " has-success"; + } else { + className += " has-warning" + } + return React.createElement(InlineInput, React.__spread({}, this.props, + {className: className, + content: this.state.content, + onChange: this.onChange, + onDone: this.onDone}) + ); + } +}); + +var RequestLine = React.createClass({displayName: "RequestLine", + render: function () { + var flow = this.props.flow; + var url = flowutils.RequestUtils.pretty_url(flow.request); + var httpver = "HTTP/" + flow.request.httpversion.join("."); + + return React.createElement("div", {className: "first-line request-line"}, + React.createElement(ValidateInlineInput, {content: flow.request.method, onChange: this.onMethodChange, isValid: this.isValidMethod}), + " ", + React.createElement(ValidateInlineInput, {content: url, onChange: this.onUrlChange, isValid: this.isValidUrl}), + " ", + React.createElement(ValidateInlineInput, {content: httpver, onChange: this.onHttpVersionChange, isValid: flowutils.isValidHttpVersion}) + ) + }, + isValidMethod: function (method) { + return true; + }, + isValidUrl: function (url) { + var u = flowutils.parseUrl(url); + return !!u.host; + }, + onMethodChange: function (nextMethod) { + actions.FlowActions.update( + this.props.flow, + {request: {method: nextMethod}} + ); + }, + onUrlChange: function (nextUrl) { + var props = flowutils.parseUrl(nextUrl); + props.path = props.path || ""; + actions.FlowActions.update( + this.props.flow, + {request: props} + ); + }, + onHttpVersionChange: function (nextVer) { + var ver = flowutils.parseHttpVersion(nextVer); + actions.FlowActions.update( + this.props.flow, + {request: {httpversion: ver}} + ); + } +}); + +var ResponseLine = React.createClass({displayName: "ResponseLine", render: function () { var flow = this.props.flow; - var first_line = [ - flow.request.method, - flowutils.RequestUtils.pretty_url(flow.request), - "HTTP/" + flow.request.httpversion.join(".") - ].join(" "); + var httpver = "HTTP/" + flow.response.httpversion.join("."); + return React.createElement("div", {className: "first-line response-line"}, + React.createElement(ValidateInlineInput, {content: httpver, onChange: this.onHttpVersionChange, isValid: flowutils.isValidHttpVersion}), + " ", + React.createElement(ValidateInlineInput, {content: flow.response.code, onChange: this.onCodeChange, isValid: this.isValidCode}), + " ", + React.createElement(ValidateInlineInput, {content: flow.response.msg, onChange: this.onMsgChange, isValid: this.isValidMsg}) - //TODO: Styling + ); + }, + isValidCode: function (code) { + return /^\d+$/.test(code); + }, + isValidMsg: function () { + return true; + }, + onHttpVersionChange: function (nextVer) { + var ver = flowutils.parseHttpVersion(nextVer); + actions.FlowActions.update( + this.props.flow, + {response: {httpversion: ver}} + ); + }, + onMsgChange: function(nextMsg){ + actions.FlowActions.update( + this.props.flow, + {response: {msg: nextMsg}} + ); + }, + onCodeChange: function(nextCode){ + nextCode = parseInt(nextCode); + actions.FlowActions.update( + this.props.flow, + {response: {code: nextCode}} + ); + } +}); +var Request = React.createClass({displayName: "Request", + render: function () { + var flow = this.props.flow; return ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), + React.createElement("section", {className: "request"}, + React.createElement(RequestLine, {flow: flow}), + /**/ React.createElement(Headers, {message: flow.request}), React.createElement("hr", null), React.createElement(ContentView, {flow: flow, message: flow.request}) @@ -1742,17 +1965,10 @@ var Request = React.createClass({displayName: "Request", var Response = React.createClass({displayName: "Response", render: function () { var flow = this.props.flow; - var first_line = [ - "HTTP/" + flow.response.httpversion.join("."), - flow.response.code, - flow.response.msg - ].join(" "); - - //TODO: Styling - return ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), + React.createElement("section", {className: "response"}, + /**/ + React.createElement(ResponseLine, {flow: flow}), React.createElement(Headers, {message: flow.response}), React.createElement("hr", null), React.createElement(ContentView, {flow: flow, message: flow.response}) @@ -1783,7 +1999,7 @@ module.exports = { Error: Error }; -},{"../../flow/utils.js":21,"../../utils.js":24,"./contentview.js":8,"react":"react"}],12:[function(require,module,exports){ +},{"../../actions.js":2,"../../flow/utils.js":21,"../../utils.js":24,"../common.js":4,"./contentview.js":8,"lodash":"lodash","react":"react"}],12:[function(require,module,exports){ var React = require("react"); var actions = require("../../actions.js"); @@ -2273,6 +2489,15 @@ var FlowView = require("./flowview/index.js"); var MainView = React.createClass({displayName: "MainView", mixins: [common.Navigation, common.State], + childContextTypes: { + returnFocus: React.PropTypes.func.isRequired + }, + getChildContext: function() { + return { returnFocus: this.returnFocus }; + }, + returnFocus: function(){ + this.getDOMNode().focus(); + }, getInitialState: function () { return { flows: [], @@ -4522,9 +4747,17 @@ module.exports = (function() { var _ = require("lodash"); var $ = require("jquery"); +var defaultPorts = { + "http": 80, + "https": 443 +}; + var MessageUtils = { getContentType: function (message) { - return this.get_first_header(message, /^Content-Type$/i).split(";")[0].trim(); + var ct = this.get_first_header(message, /^Content-Type$/i); + if(ct){ + return ct.split(";")[0].trim(); + } }, get_first_header: function (message, regex) { //FIXME: Cache Invalidation. @@ -4557,25 +4790,20 @@ var MessageUtils = { } return false; }, - getContentURL: function(flow, message){ - if(message === flow.request){ + getContentURL: function (flow, message) { + if (message === flow.request) { message = "request"; - } else if (message === flow.response){ + } else if (message === flow.response) { message = "response"; } return "/flows/" + flow.id + "/" + message + "/content"; }, - getContent: function(flow, message){ + getContent: function (flow, message) { var url = MessageUtils.getContentURL(flow, message); return $.get(url); } }; -var defaultPorts = { - "http": 80, - "https": 443 -}; - var RequestUtils = _.extend(MessageUtils, { pretty_host: function (request) { //FIXME: Add hostheader @@ -4593,10 +4821,55 @@ var RequestUtils = _.extend(MessageUtils, { var ResponseUtils = _.extend(MessageUtils, {}); +var parseUrl_regex = /^(?:(https?):\/\/)?([^\/:]+)?(?::(\d+))?(\/.*)?$/i; +var parseUrl = function (url) { + //there are many correct ways to parse a URL, + //however, a mitmproxy user may also wish to generate a not-so-correct URL. ;-) + var parts = parseUrl_regex.exec(url); + + var scheme = parts[1], + host = parts[2], + port = parseInt(parts[3]), + path = parts[4]; + if (scheme) { + port = port || defaultPorts[scheme]; + } + var ret = {}; + if (scheme) { + ret.scheme = scheme; + } + if (host) { + ret.host = host; + } + if (port) { + ret.port = port; + } + if (path) { + ret.path = path; + } + return ret; +}; + + +var isValidHttpVersion_regex = /^HTTP\/\d+(\.\d+)*$/i; +var isValidHttpVersion = function (httpVersion) { + return isValidHttpVersion_regex.test(httpVersion); +}; + +var parseHttpVersion = function (httpVersion) { + httpVersion = httpVersion.replace("HTTP/", "").split("."); + return _.map(httpVersion, function (x) { + return parseInt(x); + }); +}; + module.exports = { ResponseUtils: ResponseUtils, RequestUtils: RequestUtils, - MessageUtils: MessageUtils + MessageUtils: MessageUtils, + parseUrl: parseUrl, + parseHttpVersion: parseHttpVersion, + isValidHttpVersion: isValidHttpVersion }; },{"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ @@ -4903,6 +5176,10 @@ var $ = require("jquery"); var _ = require("lodash"); var actions = require("./actions.js"); +//debug +window.$ = $; +window._ = _; + var Key = { UP: 38, DOWN: 40, @@ -4929,17 +5206,17 @@ var formatSize = function (bytes) { if (bytes === 0) return "0"; var prefix = ["b", "kb", "mb", "gb", "tb"]; - for (var i = 0; i < prefix.length; i++){ - if (Math.pow(1024, i + 1) > bytes){ + for (var i = 0; i < prefix.length; i++) { + if (Math.pow(1024, i + 1) > bytes) { break; } } var precision; - if (bytes%Math.pow(1024, i) === 0) + if (bytes % Math.pow(1024, i) === 0) precision = 0; else precision = 1; - return (bytes/Math.pow(1024, i)).toFixed(precision) + prefix[i]; + return (bytes / Math.pow(1024, i)).toFixed(precision) + prefix[i]; }; @@ -4961,17 +5238,16 @@ var formatTimeStamp = function (seconds) { return ts.replace("T", " ").replace("Z", ""); }; - // At some places, we need to sort strings alphabetically descending, // but we can only provide a key function. // This beauty "reverses" a JS string. var end = String.fromCharCode(0xffff); -function reverseString(s){ +function reverseString(s) { return String.fromCharCode.apply(String, - _.map(s.split(""), function (c) { - return 0xffff - c.charCodeAt(0); - }) - ) + end; + _.map(s.split(""), function (c) { + return 0xffff - c.charCodeAt(0); + }) + ) + end; } function getCookie(name) { @@ -4983,21 +5259,22 @@ var xsrf = $.param({_xsrf: getCookie("_xsrf")}); //Tornado XSRF Protection. $.ajaxPrefilter(function (options) { if (["post", "put", "delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") { - if (options.data) { - options.data += ("&" + xsrf); + if(options.url.indexOf("?") === -1){ + options.url += "?" + xsrf; } else { - options.data = xsrf; + options.url += "&" + xsrf; } } }); // Log AJAX Errors $(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) { - if(thrownError === "abort"){ + if (thrownError === "abort") { return; } var message = jqXHR.responseText; console.error(thrownError, message, arguments); actions.EventLogActions.add_event(thrownError + ": " + message); + alert(message); }); module.exports = { @@ -5005,7 +5282,7 @@ module.exports = { formatTimeDelta: formatTimeDelta, formatTimeStamp: formatTimeStamp, reverseString: reverseString, - Key: Key + Key: Key, }; },{"./actions.js":2,"jquery":"jquery","lodash":"lodash"}]},{},[3]) -- cgit v1.2.3 From d65a4b610007c0ca60fa3f9ac9df06e74d6beb99 Mon Sep 17 00:00:00 2001 From: vzvu3k6k Date: Fri, 27 Mar 2015 04:18:02 +0900 Subject: web: upgrade to react-router 0.13.2 --- libmproxy/web/static/app.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index e972e298..1528d382 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -506,7 +506,6 @@ var Navigation = _.extend({}, ReactRouter.Navigation, { q[i] = dict[i] || undefined; //falsey values shall be removed. } } - q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/957 this.replaceWith(this.context.router.getCurrentPath(), this.context.router.getCurrentParams(), q); }, replaceWith: function(routeNameOrPath, params, query) { @@ -520,8 +519,6 @@ var Navigation = _.extend({}, ReactRouter.Navigation, { query = this.context.router.getCurrentQuery(); } - // FIXME: react-router is just broken, - // we hopefully just need to wait for the next release with https://github.com/rackt/react-router/pull/957. this.context.router.replaceWith(routeNameOrPath, params, query); } }); @@ -676,7 +673,7 @@ var LogMessage = React.createClass({displayName: "LogMessage", } return ( React.createElement("div", null, - indicator, " ", entry.message + indicator, " ", entry.message ) ); }, -- cgit v1.2.3 From 42cd942b64e53db9feb5f6c8b2a95669e97b1230 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 27 Mar 2015 15:30:19 +0100 Subject: web: initial attempt at header editor --- libmproxy/web/static/app.js | 159 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 135 insertions(+), 24 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index e972e298..efd2bf95 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -676,7 +676,7 @@ var LogMessage = React.createClass({displayName: "LogMessage", } return ( React.createElement("div", null, - indicator, " ", entry.message + indicator, " ", entry.message ) ); }, @@ -1724,15 +1724,77 @@ var utils = require("../../utils.js"); var ContentView = require("./contentview.js"); var Headers = React.createClass({displayName: "Headers", + propTypes: { + onChange: React.PropTypes.func.isRequired, + message: React.PropTypes.object.isRequired + }, + onChange: function (row, col, val) { + var nextHeaders = _.cloneDeep(this.props.message.headers); + nextHeaders[row][col] = val; + if (!nextHeaders[row][0] && !nextHeaders[row][1]) { + // do not delete last row + if (nextHeaders.length === 1) { + nextHeaders[0][0] = "Name"; + nextHeaders[0][1] = "Value"; + } else { + nextHeaders.splice(row, 1); + // manually move selection target if this has been the last row. + if(row === nextHeaders.length){ + this._nextSel = (row-1)+"-value"; + } + } + } + this.props.onChange(nextHeaders); + }, + onTab: function (row, col, e) { + var headers = this.props.message.headers; + if (row === headers.length - 1 && col === 1) { + e.preventDefault(); + + var nextHeaders = _.cloneDeep(this.props.message.headers); + nextHeaders.push(["Name", "Value"]); + this.props.onChange(nextHeaders); + this._nextSel = (row + 1) + "-key"; + } + }, + componentDidUpdate: function () { + if (this._nextSel && this.refs[this._nextSel]) { + this.refs[this._nextSel].focus(); + this._nextSel = undefined; + } + }, + onRemove: function (row, col, e) { + if (col === 1) { + e.preventDefault(); + this.refs[row + "-key"].focus(); + } else if (row > 0) { + e.preventDefault(); + this.refs[(row - 1) + "-value"].focus(); + } + }, render: function () { + var rows = this.props.message.headers.map(function (header, i) { + + var kEdit = React.createElement(HeaderInlineInput, { + ref: i + "-key", + content: header[0], + onChange: this.onChange.bind(null, i, 0), + onRemove: this.onRemove.bind(null, i, 0), + onTab: this.onTab.bind(null, i, 0)}); + var vEdit = React.createElement(HeaderInlineInput, { + ref: i + "-value", + content: header[1], + onChange: this.onChange.bind(null, i, 1), + onRemove: this.onRemove.bind(null, i, 1), + onTab: this.onTab.bind(null, i, 1)}); return ( React.createElement("tr", {key: i}, - React.createElement("td", {className: "header-name"}, header[0] + ":"), - React.createElement("td", {className: "header-value"}, header[1]) + React.createElement("td", {className: "header-name"}, kEdit, ":"), + React.createElement("td", {className: "header-value"}, vEdit) ) ); - }); + }.bind(this)); return ( React.createElement("table", {className: "header-table"}, React.createElement("tbody", null, @@ -1743,8 +1805,13 @@ var Headers = React.createClass({displayName: "Headers", } }); + var InlineInput = React.createClass({displayName: "InlineInput", mixins: [common.ChildFocus], + propTypes: { + content: React.PropTypes.string.isRequired, //must be string to match strict equality. + onChange: React.PropTypes.func.isRequired, + }, getInitialState: function () { return { editable: false @@ -1781,10 +1848,11 @@ var InlineInput = React.createClass({displayName: "InlineInput", } break; default: + this.props.onKeyDown && this.props.onKeyDown(e); break; } }, - blur: function(){ + blur: function () { this.getDOMNode().blur(); this.context.returnFocus && this.context.returnFocus(); }, @@ -1814,17 +1882,49 @@ var InlineInput = React.createClass({displayName: "InlineInput", } }); +var HeaderInlineInput = React.createClass({displayName: "HeaderInlineInput", + render: function () { + return React.createElement(InlineInput, React.__spread({ref: "input"}, this.props, {onKeyDown: this.onKeyDown})); + }, + focus: function () { + this.getDOMNode().focus(); + }, + onKeyDown: function (e) { + switch (e.keyCode) { + case utils.Key.BACKSPACE: + var s = window.getSelection().getRangeAt(0); + if (s.startOffset === 0 && s.endOffset === 0) { + this.props.onRemove(e); + } + break; + case utils.Key.TAB: + if(!e.shiftKey){ + this.props.onTab(e); + } + break; + } + } +}); + var ValidateInlineInput = React.createClass({displayName: "ValidateInlineInput", + propTypes: { + onChange: React.PropTypes.func.isRequired, + isValid: React.PropTypes.func.isRequired, + immediate: React.PropTypes.bool + }, getInitialState: function () { return { - content: ""+this.props.content, - originalContent: ""+this.props.content + content: this.props.content, + originalContent: this.props.content }; }, onChange: function (val) { this.setState({ content: val }); + if (this.props.immediate && val !== this.state.originalContent && this.props.isValid(val)) { + this.props.onChange(val); + } }, onDone: function () { if (this.state.content === this.state.originalContent) { @@ -1841,8 +1941,8 @@ var ValidateInlineInput = React.createClass({displayName: "ValidateInlineInput", componentWillReceiveProps: function (nextProps) { if (nextProps.content !== this.state.content) { this.setState({ - content: ""+nextProps.content, - originalContent: ""+nextProps.content + content: nextProps.content, + originalContent: nextProps.content }) } }, @@ -1869,16 +1969,13 @@ var RequestLine = React.createClass({displayName: "RequestLine", var httpver = "HTTP/" + flow.request.httpversion.join("."); return React.createElement("div", {className: "first-line request-line"}, - React.createElement(ValidateInlineInput, {content: flow.request.method, onChange: this.onMethodChange, isValid: this.isValidMethod}), + React.createElement(InlineInput, {content: flow.request.method, onChange: this.onMethodChange}), " ", React.createElement(ValidateInlineInput, {content: url, onChange: this.onUrlChange, isValid: this.isValidUrl}), " ", - React.createElement(ValidateInlineInput, {content: httpver, onChange: this.onHttpVersionChange, isValid: flowutils.isValidHttpVersion}) + React.createElement(ValidateInlineInput, {immediate: true, content: httpver, onChange: this.onHttpVersionChange, isValid: flowutils.isValidHttpVersion}) ) }, - isValidMethod: function (method) { - return true; - }, isValidUrl: function (url) { var u = flowutils.parseUrl(url); return !!u.host; @@ -1911,20 +2008,17 @@ var ResponseLine = React.createClass({displayName: "ResponseLine", var flow = this.props.flow; var httpver = "HTTP/" + flow.response.httpversion.join("."); return React.createElement("div", {className: "first-line response-line"}, - React.createElement(ValidateInlineInput, {content: httpver, onChange: this.onHttpVersionChange, isValid: flowutils.isValidHttpVersion}), + React.createElement(ValidateInlineInput, {immediate: true, content: httpver, onChange: this.onHttpVersionChange, isValid: flowutils.isValidHttpVersion}), " ", - React.createElement(ValidateInlineInput, {content: flow.response.code, onChange: this.onCodeChange, isValid: this.isValidCode}), + React.createElement(ValidateInlineInput, {immediate: true, content: flow.response.code + "", onChange: this.onCodeChange, isValid: this.isValidCode}), " ", - React.createElement(ValidateInlineInput, {content: flow.response.msg, onChange: this.onMsgChange, isValid: this.isValidMsg}) + React.createElement(InlineInput, {content: flow.response.msg, onChange: this.onMsgChange}) ); }, isValidCode: function (code) { return /^\d+$/.test(code); }, - isValidMsg: function () { - return true; - }, onHttpVersionChange: function (nextVer) { var ver = flowutils.parseHttpVersion(nextVer); actions.FlowActions.update( @@ -1932,13 +2026,13 @@ var ResponseLine = React.createClass({displayName: "ResponseLine", {response: {httpversion: ver}} ); }, - onMsgChange: function(nextMsg){ + onMsgChange: function (nextMsg) { actions.FlowActions.update( this.props.flow, {response: {msg: nextMsg}} ); }, - onCodeChange: function(nextCode){ + onCodeChange: function (nextCode) { nextCode = parseInt(nextCode); actions.FlowActions.update( this.props.flow, @@ -1954,11 +2048,18 @@ var Request = React.createClass({displayName: "Request", React.createElement("section", {className: "request"}, React.createElement(RequestLine, {flow: flow}), /**/ - React.createElement(Headers, {message: flow.request}), + React.createElement(Headers, {message: flow.request, onChange: this.onHeaderChange}), React.createElement("hr", null), React.createElement(ContentView, {flow: flow, message: flow.request}) ) ); + }, + onHeaderChange: function (nextHeaders) { + actions.FlowActions.update(this.props.flow, { + request: { + headers: nextHeaders + } + }); } }); @@ -1969,11 +2070,18 @@ var Response = React.createClass({displayName: "Response", React.createElement("section", {className: "response"}, /**/ React.createElement(ResponseLine, {flow: flow}), - React.createElement(Headers, {message: flow.response}), + React.createElement(Headers, {message: flow.response, onChange: this.onHeaderChange}), React.createElement("hr", null), React.createElement(ContentView, {flow: flow, message: flow.response}) ) ); + }, + onHeaderChange: function (nextHeaders) { + actions.FlowActions.update(this.props.flow, { + response: { + headers: nextHeaders + } + }); } }); @@ -4826,6 +4934,9 @@ var parseUrl = function (url) { //there are many correct ways to parse a URL, //however, a mitmproxy user may also wish to generate a not-so-correct URL. ;-) var parts = parseUrl_regex.exec(url); + if(!parts){ + return false; + } var scheme = parts[1], host = parts[2], -- cgit v1.2.3 From dcb42b3016415865848cdc2607ff640c1fd50818 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 27 Mar 2015 16:54:21 +0100 Subject: web: fix react-router --- libmproxy/web/static/app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 044b9e35..24f4948e 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -528,10 +528,12 @@ var Navigation = _.extend({}, ReactRouter.Navigation, { // we may remove this mixin and access react-router directly again. var State = _.extend({}, ReactRouter.State, { getQuery: function(){ - return this.context.router.getCurrentQuery(); + // For whatever reason, react-router always returns the same object, which makes comparing + // the current props with nextProps impossible. As a workaround, we just clone the query object. + return _.clone(this.context.router.getCurrentQuery()); }, getParams: function(){ - return this.context.router.getCurrentParams(); + return _.clone(this.context.router.getCurrentParams()); } }); -- cgit v1.2.3 From 1913975fa60c76bfb7e79a908b18e7e93793f71f Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 27 Mar 2015 21:58:04 +0100 Subject: web: use contexts to pass down stores. Using contexts frees us from the contracts we have using props - namely, we can assume them to be constant for the lifetime of the object. --- libmproxy/web/static/app.js | 268 ++++++++++++++++++++++---------------------- 1 file changed, 131 insertions(+), 137 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 24f4948e..f5010c90 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -468,8 +468,8 @@ var AutoScrollMixin = { componentWillUpdate: function () { var node = this.getDOMNode(); this._shouldScrollBottom = ( - node.scrollTop !== 0 && - node.scrollTop + node.clientHeight === node.scrollHeight + node.scrollTop !== 0 && + node.scrollTop + node.clientHeight === node.scrollHeight ); }, componentDidUpdate: function () { @@ -490,32 +490,54 @@ var StickyHeadMixin = { } }; +var SettingsState = { + contextTypes: { + settingsStore: React.PropTypes.object.isRequired + }, + getInitialState: function () { + return { + settings: this.context.settingsStore.dict + }; + }, + componentDidMount: function () { + this.context.settingsStore.addListener("recalculate", this.onSettingsChange); + }, + componentWillUnmount: function () { + this.context.settingsStore.removeListener("recalculate", this.onSettingsChange); + }, + onSettingsChange: function () { + this.setState({ + settings: this.context.settingsStore.dict + }); + }, +}; + var ChildFocus = { - contextTypes: { - returnFocus: React.PropTypes.func - } + contextTypes: { + returnFocus: React.PropTypes.func + } }; var Navigation = _.extend({}, ReactRouter.Navigation, { setQuery: function (dict) { var q = this.context.router.getCurrentQuery(); - for(var i in dict){ - if(dict.hasOwnProperty(i)){ + for (var i in dict) { + if (dict.hasOwnProperty(i)) { q[i] = dict[i] || undefined; //falsey values shall be removed. } } this.replaceWith(this.context.router.getCurrentPath(), this.context.router.getCurrentParams(), q); }, - replaceWith: function(routeNameOrPath, params, query) { - if(routeNameOrPath === undefined){ + replaceWith: function (routeNameOrPath, params, query) { + if (routeNameOrPath === undefined) { routeNameOrPath = this.context.router.getCurrentPath(); } - if(params === undefined){ + if (params === undefined) { params = this.context.router.getCurrentParams(); } - if(query === undefined) { + if (query === undefined) { query = this.context.router.getCurrentQuery(); } @@ -526,13 +548,13 @@ var Navigation = _.extend({}, ReactRouter.Navigation, { // react-router is fairly good at changing its API regularly. // We keep the old method for now - if it should turn out that their changes are permanent, // we may remove this mixin and access react-router directly again. -var State = _.extend({}, ReactRouter.State, { - getQuery: function(){ +var RouterState = _.extend({}, ReactRouter.State, { + getQuery: function () { // For whatever reason, react-router always returns the same object, which makes comparing // the current props with nextProps impossible. As a workaround, we just clone the query object. return _.clone(this.context.router.getCurrentQuery()); }, - getParams: function(){ + getParams: function () { return _.clone(this.context.router.getCurrentParams()); } }); @@ -644,11 +666,12 @@ var Splitter = React.createClass({displayName: "Splitter", module.exports = { ChildFocus: ChildFocus, - State: State, + RouterState: RouterState, Navigation: Navigation, StickyHeadMixin: StickyHeadMixin, AutoScrollMixin: AutoScrollMixin, - Splitter: Splitter + Splitter: Splitter, + SettingsState: SettingsState }; },{"lodash":"lodash","react":"react","react-router":"react-router"}],5:[function(require,module,exports){ @@ -685,46 +708,37 @@ var LogMessage = React.createClass({displayName: "LogMessage", }); var EventLogContents = React.createClass({displayName: "EventLogContents", + contextTypes: { + eventStore: React.PropTypes.object.isRequired + }, mixins: [common.AutoScrollMixin, VirtualScrollMixin], getInitialState: function () { - return { - log: [] - }; - }, - componentWillMount: function () { - this.openView(this.props.eventStore); - }, - componentWillUnmount: function () { - this.closeView(); - }, - openView: function (store) { - var view = new views.StoreView(store, function (entry) { + var filterFn = function (entry) { return this.props.filter[entry.level]; - }.bind(this)); - this.setState({ - view: view - }); - + }; + var view = new views.StoreView(this.context.eventStore, filterFn.bind(this)); view.addListener("add", this.onEventLogChange); view.addListener("recalculate", this.onEventLogChange); + + return { + log: view.list, + view: view + }; }, - closeView: function () { + componentWillUnmount: function () { this.state.view.close(); }, + filter: function (entry) { + return this.props.filter[entry.level]; + }, onEventLogChange: function () { - this.setState({ - log: this.state.view.list - }); + this.forceUpdate(); }, componentWillReceiveProps: function (nextProps) { if (nextProps.filter !== this.props.filter) { this.props.filter = nextProps.filter; // Dirty: Make sure that view filter sees the update. this.state.view.recalculate(); } - if (nextProps.eventStore !== this.props.eventStore) { - this.closeView(); - this.openView(nextProps.eventStore); - } }, getDefaultProps: function () { return { @@ -803,7 +817,7 @@ var EventLog = React.createClass({displayName: "EventLog", ) ), - React.createElement(EventLogContents, {filter: this.state.filter, eventStore: this.props.eventStore}) + React.createElement(EventLogContents, {filter: this.state.filter}) ) ); } @@ -1125,33 +1139,25 @@ var ROW_HEIGHT = 32; var FlowTable = React.createClass({displayName: "FlowTable", mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin], + contextTypes: { + view: React.PropTypes.object.isRequired + }, getInitialState: function () { return { columns: flowtable_columns }; }, - _listen: function(view){ - if(!view){ - return; - } - view.addListener("add", this.onChange); - view.addListener("update", this.onChange); - view.addListener("remove", this.onChange); - view.addListener("recalculate", this.onChange); - }, componentWillMount: function () { - this._listen(this.props.view); + this.context.view.addListener("add", this.onChange); + this.context.view.addListener("update", this.onChange); + this.context.view.addListener("remove", this.onChange); + this.context.view.addListener("recalculate", this.onChange); }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.view !== this.props.view) { - if (this.props.view) { - this.props.view.removeListener("add"); - this.props.view.removeListener("update"); - this.props.view.removeListener("remove"); - this.props.view.removeListener("recalculate"); - } - this._listen(nextProps.view); - } + componentWillUnmount: function(){ + this.context.view.removeListener("add", this.onChange); + this.context.view.removeListener("update", this.onChange); + this.context.view.removeListener("remove", this.onChange); + this.context.view.removeListener("recalculate", this.onChange); }, getDefaultProps: function () { return { @@ -1167,7 +1173,7 @@ var FlowTable = React.createClass({displayName: "FlowTable", }, scrollIntoView: function (flow) { this.scrollRowIntoView( - this.props.view.index(flow), + this.context.view.index(flow), this.refs.body.getDOMNode().offsetTop ); }, @@ -1175,8 +1181,8 @@ var FlowTable = React.createClass({displayName: "FlowTable", var selected = (flow === this.props.selected); var highlighted = ( - this.props.view._highlight && - this.props.view._highlight[flow.id] + this.context.view._highlight && + this.context.view._highlight[flow.id] ); return React.createElement(FlowRow, {key: flow.id, @@ -1189,9 +1195,7 @@ var FlowTable = React.createClass({displayName: "FlowTable", ); }, render: function () { - //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); - var flows = this.props.view ? this.props.view.list : []; - + var flows = this.context.view.list; var rows = this.renderRows(flows); return ( @@ -1653,7 +1657,7 @@ var allTabs = { }; var FlowView = React.createClass({displayName: "FlowView", - mixins: [common.StickyHeadMixin, common.Navigation, common.State], + mixins: [common.StickyHeadMixin, common.Navigation, common.RouterState], getTabs: function (flow) { var tabs = []; ["request", "response", "error"].forEach(function (e) { @@ -2171,11 +2175,13 @@ module.exports = Nav; },{"../../actions.js":2,"react":"react"}],13:[function(require,module,exports){ var React = require("react"); +var common = require("./common.js"); var Footer = React.createClass({displayName: "Footer", + mixins: [common.SettingsState], render: function () { - var mode = this.props.settings.mode; - var intercept = this.props.settings.intercept; + var mode = this.state.settings.mode; + var intercept = this.state.settings.intercept; return ( React.createElement("footer", null, mode != "regular" ? React.createElement("span", {className: "label label-success"}, mode, " mode") : null, @@ -2188,7 +2194,7 @@ var Footer = React.createClass({displayName: "Footer", module.exports = Footer; -},{"react":"react"}],14:[function(require,module,exports){ +},{"./common.js":4,"react":"react"}],14:[function(require,module,exports){ var React = require("react"); var $ = require("jquery"); @@ -2348,7 +2354,7 @@ var FilterInput = React.createClass({displayName: "FilterInput", }); var MainMenu = React.createClass({displayName: "MainMenu", - mixins: [common.Navigation, common.State], + mixins: [common.Navigation, common.RouterState, common.SettingsState], statics: { title: "Start", route: "flows" @@ -2369,7 +2375,7 @@ var MainMenu = React.createClass({displayName: "MainMenu", render: function () { var filter = this.getQuery()[Query.FILTER] || ""; var highlight = this.getQuery()[Query.HIGHLIGHT] || ""; - var intercept = this.props.settings.intercept || ""; + var intercept = this.state.settings.intercept || ""; return ( React.createElement("div", null, @@ -2405,7 +2411,7 @@ var ViewMenu = React.createClass({displayName: "ViewMenu", title: "View", route: "flows" }, - mixins: [common.Navigation, common.State], + mixins: [common.Navigation, common.RouterState], toggleEventLog: function () { var d = {}; @@ -2570,7 +2576,7 @@ var Header = React.createClass({displayName: "Header", header ), React.createElement("div", {className: "menu"}, - React.createElement(this.state.active, {settings: this.props.settings}) + React.createElement(this.state.active, null) ) ) ); @@ -2580,7 +2586,7 @@ var Header = React.createClass({displayName: "Header", module.exports = { Header: Header -} +}; },{"../actions.js":2,"../filt/filt.js":20,"../utils.js":24,"./common.js":4,"jquery":"jquery","react":"react"}],15:[function(require,module,exports){ var React = require("react"); @@ -2595,22 +2601,40 @@ FlowTable = require("./flowtable.js"); var FlowView = require("./flowview/index.js"); var MainView = React.createClass({displayName: "MainView", - mixins: [common.Navigation, common.State], + mixins: [common.Navigation, common.RouterState], + contextTypes: { + flowStore: React.PropTypes.object.isRequired, + }, childContextTypes: { - returnFocus: React.PropTypes.func.isRequired + returnFocus: React.PropTypes.func.isRequired, + view: React.PropTypes.object.isRequired, }, - getChildContext: function() { - return { returnFocus: this.returnFocus }; + getChildContext: function () { + return { + returnFocus: this.returnFocus, + view: this.state.view + }; }, - returnFocus: function(){ + returnFocus: function () { this.getDOMNode().focus(); }, getInitialState: function () { + var sortKeyFun = false; + var view = new views.StoreView(this.context.flowStore, this.getViewFilt(), sortKeyFun); + view.addListener("recalculate", this.onRecalculate); + view.addListener("add", this.onUpdate); + view.addListener("update", this.onUpdate); + view.addListener("remove", this.onUpdate); + view.addListener("remove", this.onRemove); + return { - flows: [], - sortKeyFun: false + view: view, + sortKeyFun: sortKeyFun }; }, + componentWillUnmount: function () { + this.state.view.close(); + }, getViewFilt: function () { try { var filt = Filt.parse(this.getQuery()[Query.FILTER] || ""); @@ -2629,29 +2653,12 @@ var MainView = React.createClass({displayName: "MainView", }; }, componentWillReceiveProps: function (nextProps) { - if (nextProps.flowStore !== this.props.flowStore) { - this.closeView(); - this.openView(nextProps.flowStore); - } - var filterChanged = (this.props.query[Query.FILTER] !== nextProps.query[Query.FILTER]); var highlightChanged = (this.props.query[Query.HIGHLIGHT] !== nextProps.query[Query.HIGHLIGHT]); if (filterChanged || highlightChanged) { this.state.view.recalculate(this.getViewFilt(), this.state.sortKeyFun); } }, - openView: function (store) { - var view = new views.StoreView(store, this.getViewFilt(), this.state.sortKeyFun); - this.setState({ - view: view - }); - - view.addListener("recalculate", this.onRecalculate); - view.addListener("add", this.onUpdate); - view.addListener("update", this.onUpdate); - view.addListener("remove", this.onUpdate); - view.addListener("remove", this.onRemove); - }, onRecalculate: function () { this.forceUpdate(); var selected = this.getSelected(); @@ -2670,16 +2677,7 @@ var MainView = React.createClass({displayName: "MainView", this.selectFlow(flow_to_select); } }, - closeView: function () { - this.state.view.close(); - }, - componentWillMount: function () { - this.openView(this.props.flowStore); - }, - componentWillUnmount: function () { - this.closeView(); - }, - setSortKeyFun: function(sortKeyFun){ + setSortKeyFun: function (sortKeyFun) { this.setState({ sortKeyFun: sortKeyFun }); @@ -2806,7 +2804,7 @@ var MainView = React.createClass({displayName: "MainView", e.preventDefault(); }, getSelected: function () { - return this.props.flowStore.get(this.getParams().flowId); + return this.context.flowStore.get(this.getParams().flowId); }, render: function () { var selected = this.getSelected(); @@ -2824,7 +2822,6 @@ var MainView = React.createClass({displayName: "MainView", return ( React.createElement("div", {className: "main-view", onKeyDown: this.onKeyDown, tabIndex: "0"}, React.createElement(FlowTable, {ref: "flowTable", - view: this.state.view, selectFlow: this.selectFlow, setSortKeyFun: this.setSortKeyFun, selected: selected}), @@ -2860,52 +2857,48 @@ var Reports = React.createClass({displayName: "Reports", var ProxyAppMain = React.createClass({displayName: "ProxyAppMain", - mixins: [common.State], + mixins: [common.RouterState], + childContextTypes: { + settingsStore: React.PropTypes.object.isRequired, + flowStore: React.PropTypes.object.isRequired, + eventStore: React.PropTypes.object.isRequired + }, + getChildContext: function () { + return { + settingsStore: this.state.settingsStore, + flowStore: this.state.flowStore, + eventStore: this.state.eventStore + }; + }, getInitialState: function () { var eventStore = new store.EventLogStore(); var flowStore = new store.FlowStore(); - var settings = new store.SettingsStore(); + var settingsStore = new store.SettingsStore(); // Default Settings before fetch - _.extend(settings.dict,{ - }); + _.extend(settingsStore.dict, {}); return { - settings: settings, + settingsStore: settingsStore, flowStore: flowStore, eventStore: eventStore }; }, - componentDidMount: function () { - this.state.settings.addListener("recalculate", this.onSettingsChange); - window.app = this; - }, - componentWillUnmount: function () { - this.state.settings.removeListener("recalculate", this.onSettingsChange); - }, - onSettingsChange: function(){ - this.setState({ - settings: this.state.settings - }); - }, render: function () { var eventlog; if (this.getQuery()[Query.SHOW_EVENTLOG]) { eventlog = [ React.createElement(common.Splitter, {key: "splitter", axis: "y"}), - React.createElement(EventLog, {key: "eventlog", eventStore: this.state.eventStore}) + React.createElement(EventLog, {key: "eventlog"}) ]; } else { eventlog = null; } return ( React.createElement("div", {id: "container"}, - React.createElement(header.Header, {settings: this.state.settings.dict}), - React.createElement(RouteHandler, { - settings: this.state.settings.dict, - flowStore: this.state.flowStore, - query: this.getQuery()}), + React.createElement(header.Header, null), + React.createElement(RouteHandler, {query: this.getQuery()}), eventlog, - React.createElement(Footer, {settings: this.state.settings.dict}) + React.createElement(Footer, null) ) ); } @@ -5203,6 +5196,7 @@ _.extend(StoreView.prototype, EventEmitter.prototype, { this.store.removeListener("update", this.update); this.store.removeListener("remove", this.remove); this.store.removeListener("recalculate", this.recalculate); + this.removeAllListeners(); }, recalculate: function (filt, sortfun) { filt = filt || this.filt || default_filt; -- cgit v1.2.3 From fd911b75e6a59c1050815a6c183c4eb32aaabb98 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 29 Mar 2015 01:45:37 +0100 Subject: web: global key handling --- libmproxy/web/static/app.js | 21932 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 21904 insertions(+), 28 deletions(-) (limited to 'libmproxy/web/static/app.js') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index f5010c90..9de3ccf2 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -1,4 +1,21771 @@ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 8 && documentMode <= 11) + ) +); + +/** + * Opera <= 12 includes TextEvent in window, but does not fire + * text input events. Rely on keypress instead. + */ +function isPresto() { + var opera = window.opera; + return ( + typeof opera === 'object' && + typeof opera.version === 'function' && + parseInt(opera.version(), 10) <= 12 + ); +} + +var SPACEBAR_CODE = 32; +var SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE); + +var topLevelTypes = EventConstants.topLevelTypes; + +// Events and their corresponding property names. +var eventTypes = { + beforeInput: { + phasedRegistrationNames: { + bubbled: keyOf({onBeforeInput: null}), + captured: keyOf({onBeforeInputCapture: null}) + }, + dependencies: [ + topLevelTypes.topCompositionEnd, + topLevelTypes.topKeyPress, + topLevelTypes.topTextInput, + topLevelTypes.topPaste + ] + }, + compositionEnd: { + phasedRegistrationNames: { + bubbled: keyOf({onCompositionEnd: null}), + captured: keyOf({onCompositionEndCapture: null}) + }, + dependencies: [ + topLevelTypes.topBlur, + topLevelTypes.topCompositionEnd, + topLevelTypes.topKeyDown, + topLevelTypes.topKeyPress, + topLevelTypes.topKeyUp, + topLevelTypes.topMouseDown + ] + }, + compositionStart: { + phasedRegistrationNames: { + bubbled: keyOf({onCompositionStart: null}), + captured: keyOf({onCompositionStartCapture: null}) + }, + dependencies: [ + topLevelTypes.topBlur, + topLevelTypes.topCompositionStart, + topLevelTypes.topKeyDown, + topLevelTypes.topKeyPress, + topLevelTypes.topKeyUp, + topLevelTypes.topMouseDown + ] + }, + compositionUpdate: { + phasedRegistrationNames: { + bubbled: keyOf({onCompositionUpdate: null}), + captured: keyOf({onCompositionUpdateCapture: null}) + }, + dependencies: [ + topLevelTypes.topBlur, + topLevelTypes.topCompositionUpdate, + topLevelTypes.topKeyDown, + topLevelTypes.topKeyPress, + topLevelTypes.topKeyUp, + topLevelTypes.topMouseDown + ] + } +}; + +// Track whether we've ever handled a keypress on the space key. +var hasSpaceKeypress = false; + +/** + * Return whether a native keypress event is assumed to be a command. + * This is required because Firefox fires `keypress` events for key commands + * (cut, copy, select-all, etc.) even though no character is inserted. + */ +function isKeypressCommand(nativeEvent) { + return ( + (nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) && + // ctrlKey && altKey is equivalent to AltGr, and is not a command. + !(nativeEvent.ctrlKey && nativeEvent.altKey) + ); +} + + +/** + * Translate native top level events into event types. + * + * @param {string} topLevelType + * @return {object} + */ +function getCompositionEventType(topLevelType) { + switch (topLevelType) { + case topLevelTypes.topCompositionStart: + return eventTypes.compositionStart; + case topLevelTypes.topCompositionEnd: + return eventTypes.compositionEnd; + case topLevelTypes.topCompositionUpdate: + return eventTypes.compositionUpdate; + } +} + +/** + * Does our fallback best-guess model think this event signifies that + * composition has begun? + * + * @param {string} topLevelType + * @param {object} nativeEvent + * @return {boolean} + */ +function isFallbackCompositionStart(topLevelType, nativeEvent) { + return ( + topLevelType === topLevelTypes.topKeyDown && + nativeEvent.keyCode === START_KEYCODE + ); +} + +/** + * Does our fallback mode think that this event is the end of composition? + * + * @param {string} topLevelType + * @param {object} nativeEvent + * @return {boolean} + */ +function isFallbackCompositionEnd(topLevelType, nativeEvent) { + switch (topLevelType) { + case topLevelTypes.topKeyUp: + // Command keys insert or clear IME input. + return (END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1); + case topLevelTypes.topKeyDown: + // Expect IME keyCode on each keydown. If we get any other + // code we must have exited earlier. + return (nativeEvent.keyCode !== START_KEYCODE); + case topLevelTypes.topKeyPress: + case topLevelTypes.topMouseDown: + case topLevelTypes.topBlur: + // Events are not possible without cancelling IME. + return true; + default: + return false; + } +} + +/** + * Google Input Tools provides composition data via a CustomEvent, + * with the `data` property populated in the `detail` object. If this + * is available on the event object, use it. If not, this is a plain + * composition event and we have nothing special to extract. + * + * @param {object} nativeEvent + * @return {?string} + */ +function getDataFromCustomEvent(nativeEvent) { + var detail = nativeEvent.detail; + if (typeof detail === 'object' && 'data' in detail) { + return detail.data; + } + return null; +} + +// Track the current IME composition fallback object, if any. +var currentComposition = null; + +/** + * @param {string} topLevelType Record from `EventConstants`. + * @param {DOMEventTarget} topLevelTarget The listening component root node. + * @param {string} topLevelTargetID ID of `topLevelTarget`. + * @param {object} nativeEvent Native browser event. + * @return {?object} A SyntheticCompositionEvent. + */ +function extractCompositionEvent( + topLevelType, + topLevelTarget, + topLevelTargetID, + nativeEvent +) { + var eventType; + var fallbackData; + + if (canUseCompositionEvent) { + eventType = getCompositionEventType(topLevelType); + } else if (!currentComposition) { + if (isFallbackCompositionStart(topLevelType, nativeEvent)) { + eventType = eventTypes.compositionStart; + } + } else if (isFallbackCompositionEnd(topLevelType, nativeEvent)) { + eventType = eventTypes.compositionEnd; + } + + if (!eventType) { + return null; + } + + if (useFallbackCompositionData) { + // The current composition is stored statically and must not be + // overwritten while composition continues. + if (!currentComposition && eventType === eventTypes.compositionStart) { + currentComposition = FallbackCompositionState.getPooled(topLevelTarget); + } else if (eventType === eventTypes.compositionEnd) { + if (currentComposition) { + fallbackData = currentComposition.getData(); + } + } + } + + var event = SyntheticCompositionEvent.getPooled( + eventType, + topLevelTargetID, + nativeEvent + ); + + if (fallbackData) { + // Inject data generated from fallback path into the synthetic event. + // This matches the property of native CompositionEventInterface. + event.data = fallbackData; + } else { + var customData = getDataFromCustomEvent(nativeEvent); + if (customData !== null) { + event.data = customData; + } + } + + EventPropagators.accumulateTwoPhaseDispatches(event); + return event; +} + +/** + * @param {string} topLevelType Record from `EventConstants`. + * @param {object} nativeEvent Native browser event. + * @return {?string} The string corresponding to this `beforeInput` event. + */ +function getNativeBeforeInputChars(topLevelType, nativeEvent) { + switch (topLevelType) { + case topLevelTypes.topCompositionEnd: + return getDataFromCustomEvent(nativeEvent); + case topLevelTypes.topKeyPress: + /** + * If native `textInput` events are available, our goal is to make + * use of them. However, there is a special case: the spacebar key. + * In Webkit, preventing default on a spacebar `textInput` event + * cancels character insertion, but it *also* causes the browser + * to fall back to its default spacebar behavior of scrolling the + * page. + * + * Tracking at: + * https://code.google.com/p/chromium/issues/detail?id=355103 + * + * To avoid this issue, use the keypress event as if no `textInput` + * event is available. + */ + var which = nativeEvent.which; + if (which !== SPACEBAR_CODE) { + return null; + } + + hasSpaceKeypress = true; + return SPACEBAR_CHAR; + + case topLevelTypes.topTextInput: + // Record the characters to be added to the DOM. + var chars = nativeEvent.data; + + // If it's a spacebar character, assume that we have already handled + // it at the keypress level and bail immediately. Android Chrome + // doesn't give us keycodes, so we need to blacklist it. + if (chars === SPACEBAR_CHAR && hasSpaceKeypress) { + return null; + } + + return chars; + + default: + // For other native event types, do nothing. + return null; + } +} + +/** + * For browsers that do not provide the `textInput` event, extract the + * appropriate string to use for SyntheticInputEvent. + * + * @param {string} topLevelType Record from `EventConstants`. + * @param {object} nativeEvent Native browser event. + * @return {?string} The fallback string for this `beforeInput` event. + */ +function getFallbackBeforeInputChars(topLevelType, nativeEvent) { + // If we are currently composing (IME) and using a fallback to do so, + // try to extract the composed characters from the fallback object. + if (currentComposition) { + if ( + topLevelType === topLevelTypes.topCompositionEnd || + isFallbackCompositionEnd(topLevelType, nativeEvent) + ) { + var chars = currentComposition.getData(); + FallbackCompositionState.release(currentComposition); + currentComposition = null; + return chars; + } + return null; + } + + switch (topLevelType) { + case topLevelTypes.topPaste: + // If a paste event occurs after a keypress, throw out the input + // chars. Paste events should not lead to BeforeInput events. + return null; + case topLevelTypes.topKeyPress: + /** + * As of v27, Firefox may fire keypress events even when no character + * will be inserted. A few possibilities: + * + * - `which` is `0`. Arrow keys, Esc key, etc. + * + * - `which` is the pressed key code, but no char is available. + * Ex: 'AltGr + d` in Polish. There is no modified character for + * this key combination and no character is inserted into the + * document, but FF fires the keypress for char code `100` anyway. + * No `input` event will occur. + * + * - `which` is the pressed key code, but a command combination is + * being used. Ex: `Cmd+C`. No character is inserted, and no + * `input` event will occur. + */ + if (nativeEvent.which && !isKeypressCommand(nativeEvent)) { + return String.fromCharCode(nativeEvent.which); + } + return null; + case topLevelTypes.topCompositionEnd: + return useFallbackCompositionData ? null : nativeEvent.data; + default: + return null; + } +} + +/** + * Extract a SyntheticInputEvent for `beforeInput`, based on either native + * `textInput` or fallback behavior. + * + * @param {string} topLevelType Record from `EventConstants`. + * @param {DOMEventTarget} topLevelTarget The listening component root node. + * @param {string} topLevelTargetID ID of `topLevelTarget`. + * @param {object} nativeEvent Native browser event. + * @return {?object} A SyntheticInputEvent. + */ +function extractBeforeInputEvent( + topLevelType, + topLevelTarget, + topLevelTargetID, + nativeEvent +) { + var chars; + + if (canUseTextInputEvent) { + chars = getNativeBeforeInputChars(topLevelType, nativeEvent); + } else { + chars = getFallbackBeforeInputChars(topLevelType, nativeEvent); + } + + // If no characters are being inserted, no BeforeInput event should + // be fired. + if (!chars) { + return null; + } + + var event = SyntheticInputEvent.getPooled( + eventTypes.beforeInput, + topLevelTargetID, + nativeEvent + ); + + event.data = chars; + EventPropagators.accumulateTwoPhaseDispatches(event); + return event; +} + +/** + * Create an `onBeforeInput` event to match + * http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents. + * + * This event plugin is based on the native `textInput` event + * available in Chrome, Safari, Opera, and IE. This event fires after + * `onKeyPress` and `onCompositionEnd`, but before `onInput`. + * + * `beforeInput` is spec'd but not implemented in any browsers, and + * the `input` event does not provide any useful information about what has + * actually been added, contrary to the spec. Thus, `textInput` is the best + * available event to identify the characters that have actually been inserted + * into the target node. + * + * This plugin is also responsible for emitting `composition` events, thus + * allowing us to share composition fallback code for both `beforeInput` and + * `composition` event types. + */ +var BeforeInputEventPlugin = { + + eventTypes: eventTypes, + + /** + * @param {string} topLevelType Record from `EventConstants`. + * @param {DOMEventTarget} topLevelTarget The listening component root node. + * @param {string} topLevelTargetID ID of `topLevelTarget`. + * @param {object} nativeEvent Native browser event. + * @return {*} An accumulation of synthetic events. + * @see {EventPluginHub.extractEvents} + */ + extractEvents: function( + topLevelType, + topLevelTarget, + topLevelTargetID, + nativeEvent + ) { + return [ + extractCompositionEvent( + topLevelType, + topLevelTarget, + topLevelTargetID, + nativeEvent + ), + extractBeforeInputEvent( + topLevelType, + topLevelTarget, + topLevelTargetID, + nativeEvent + ) + ]; + } +}; + +module.exports = BeforeInputEventPlugin; + +},{"./EventConstants":16,"./EventPropagators":21,"./ExecutionEnvironment":22,"./FallbackCompositionState":23,"./SyntheticCompositionEvent":107,"./SyntheticInputEvent":111,"./keyOf":158}],4:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule CSSCore + * @typechecks + */ + +var invariant = require("./invariant"); + +/** + * The CSSCore module specifies the API (and implements most of the methods) + * that should be used when dealing with the display of elements (via their + * CSS classes and visibility on screen. It is an API focused on mutating the + * display and not reading it as no logical state should be encoded in the + * display of elements. + */ + +var CSSCore = { + + /** + * Adds the class passed in to the element if it doesn't already have it. + * + * @param {DOMElement} element the element to set the class on + * @param {string} className the CSS className + * @return {DOMElement} the element passed in + */ + addClass: function(element, className) { + ("production" !== process.env.NODE_ENV ? invariant( + !/\s/.test(className), + 'CSSCore.addClass takes only a single class name. "%s" contains ' + + 'multiple classes.', className + ) : invariant(!/\s/.test(className))); + + if (className) { + if (element.classList) { + element.classList.add(className); + } else if (!CSSCore.hasClass(element, className)) { + element.className = element.className + ' ' + className; + } + } + return element; + }, + + /** + * Removes the class passed in from the element + * + * @param {DOMElement} element the element to set the class on + * @param {string} className the CSS className + * @return {DOMElement} the element passed in + */ + removeClass: function(element, className) { + ("production" !== process.env.NODE_ENV ? invariant( + !/\s/.test(className), + 'CSSCore.removeClass takes only a single class name. "%s" contains ' + + 'multiple classes.', className + ) : invariant(!/\s/.test(className))); + + if (className) { + if (element.classList) { + element.classList.remove(className); + } else if (CSSCore.hasClass(element, className)) { + element.className = element.className + .replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)', 'g'), '$1') + .replace(/\s+/g, ' ') // multiple spaces to one + .replace(/^\s*|\s*$/g, ''); // trim the ends + } + } + return element; + }, + + /** + * Helper to add or remove a class from an element based on a condition. + * + * @param {DOMElement} element the element to set the class on + * @param {string} className the CSS className + * @param {*} bool condition to whether to add or remove the class + * @return {DOMElement} the element passed in + */ + conditionClass: function(element, className, bool) { + return (bool ? CSSCore.addClass : CSSCore.removeClass)(element, className); + }, + + /** + * Tests whether the element has the class specified. + * + * @param {DOMNode|DOMWindow} element the element to set the class on + * @param {string} className the CSS className + * @return {boolean} true if the element has the class, false if not + */ + hasClass: function(element, className) { + ("production" !== process.env.NODE_ENV ? invariant( + !/\s/.test(className), + 'CSS.hasClass takes only a single class name.' + ) : invariant(!/\s/.test(className))); + if (element.classList) { + return !!className && element.classList.contains(className); + } + return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1; + } + +}; + +module.exports = CSSCore; + +}).call(this,require('_process')) + +},{"./invariant":151,"_process":174}],5:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule CSSProperty + */ + +'use strict'; + +/** + * CSS properties which accept numbers but are not in units of "px". + */ +var isUnitlessNumber = { + boxFlex: true, + boxFlexGroup: true, + columnCount: true, + flex: true, + flexGrow: true, + flexShrink: true, + fontWeight: true, + lineClamp: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + widows: true, + zIndex: true, + zoom: true, + + // SVG-related properties + fillOpacity: true, + strokeOpacity: true +}; + +/** + * @param {string} prefix vendor-specific prefix, eg: Webkit + * @param {string} key style name, eg: transitionDuration + * @return {string} style name prefixed with `prefix`, properly camelCased, eg: + * WebkitTransitionDuration + */ +function prefixKey(prefix, key) { + return prefix + key.charAt(0).toUpperCase() + key.substring(1); +} + +/** + * Support style names that may come passed in prefixed by adding permutations + * of vendor prefixes. + */ +var prefixes = ['Webkit', 'ms', 'Moz', 'O']; + +// Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an +// infinite loop, because it iterates over the newly added props too. +Object.keys(isUnitlessNumber).forEach(function(prop) { + prefixes.forEach(function(prefix) { + isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; + }); +}); + +/** + * Most style properties can be unset by doing .style[prop] = '' but IE8 + * doesn't like doing that with shorthand properties so for the properties that + * IE8 breaks on, which are listed here, we instead unset each of the + * individual properties. See http://bugs.jquery.com/ticket/12385. + * The 4-value 'clock' properties like margin, padding, border-width seem to + * behave without any problems. Curiously, list-style works too without any + * special prodding. + */ +var shorthandPropertyExpansions = { + background: { + backgroundImage: true, + backgroundPosition: true, + backgroundRepeat: true, + backgroundColor: true + }, + border: { + borderWidth: true, + borderStyle: true, + borderColor: true + }, + borderBottom: { + borderBottomWidth: true, + borderBottomStyle: true, + borderBottomColor: true + }, + borderLeft: { + borderLeftWidth: true, + borderLeftStyle: true, + borderLeftColor: true + }, + borderRight: { + borderRightWidth: true, + borderRightStyle: true, + borderRightColor: true + }, + borderTop: { + borderTopWidth: true, + borderTopStyle: true, + borderTopColor: true + }, + font: { + fontStyle: true, + fontVariant: true, + fontWeight: true, + fontSize: true, + lineHeight: true, + fontFamily: true + } +}; + +var CSSProperty = { + isUnitlessNumber: isUnitlessNumber, + shorthandPropertyExpansions: shorthandPropertyExpansions +}; + +module.exports = CSSProperty; + +},{}],6:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule CSSPropertyOperations + * @typechecks static-only + */ + +'use strict'; + +var CSSProperty = require("./CSSProperty"); +var ExecutionEnvironment = require("./ExecutionEnvironment"); + +var camelizeStyleName = require("./camelizeStyleName"); +var dangerousStyleValue = require("./dangerousStyleValue"); +var hyphenateStyleName = require("./hyphenateStyleName"); +var memoizeStringOnly = require("./memoizeStringOnly"); +var warning = require("./warning"); + +var processStyleName = memoizeStringOnly(function(styleName) { + return hyphenateStyleName(styleName); +}); + +var styleFloatAccessor = 'cssFloat'; +if (ExecutionEnvironment.canUseDOM) { + // IE8 only supports accessing cssFloat (standard) as styleFloat + if (document.documentElement.style.cssFloat === undefined) { + styleFloatAccessor = 'styleFloat'; + } +} + +if ("production" !== process.env.NODE_ENV) { + // 'msTransform' is correct, but the other prefixes should be capitalized + var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/; + + // style values shouldn't contain a semicolon + var badStyleValueWithSemicolonPattern = /;\s*$/; + + var warnedStyleNames = {}; + var warnedStyleValues = {}; + + var warnHyphenatedStyleName = function(name) { + if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) { + return; + } + + warnedStyleNames[name] = true; + ("production" !== process.env.NODE_ENV ? warning( + false, + 'Unsupported style property %s. Did you mean %s?', + name, + camelizeStyleName(name) + ) : null); + }; + + var warnBadVendoredStyleName = function(name) { + if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) { + return; + } + + warnedStyleNames[name] = true; + ("production" !== process.env.NODE_ENV ? warning( + false, + 'Unsupported vendor-prefixed style property %s. Did you mean %s?', + name, + name.charAt(0).toUpperCase() + name.slice(1) + ) : null); + }; + + var warnStyleValueWithSemicolon = function(name, value) { + if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) { + return; + } + + warnedStyleValues[value] = true; + ("production" !== process.env.NODE_ENV ? warning( + false, + 'Style property values shouldn\'t contain a semicolon. ' + + 'Try "%s: %s" instead.', + name, + value.replace(badStyleValueWithSemicolonPattern, '') + ) : null); + }; + + /** + * @param {string} name + * @param {*} value + */ + var warnValidStyle = function(name, value) { + if (name.indexOf('-') > -1) { + warnHyphenatedStyleName(name); + } else if (badVendoredStyleNamePattern.test(name)) { + warnBadVendoredStyleName(name); + } else if (badStyleValueWithSemicolonPattern.test(value)) { + warnStyleValueWithSemicolon(name, value); + } + }; +} + +/** + * Operations for dealing with CSS properties. + */ +var CSSPropertyOperations = { + + /** + * Serializes a mapping of style properties for use as inline styles: + * + * > createMarkupForStyles({width: '200px', height: 0}) + * "width:200px;height:0;" + * + * Undefined values are ignored so that declarative programming is easier. + * The result should be HTML-escaped before insertion into the DOM. + * + * @param {object} styles + * @return {?string} + */ + createMarkupForStyles: function(styles) { + var serialized = ''; + for (var styleName in styles) { + if (!styles.hasOwnProperty(styleName)) { + continue; + } + var styleValue = styles[styleName]; + if ("production" !== process.env.NODE_ENV) { + warnValidStyle(styleName, styleValue); + } + if (styleValue != null) { + serialized += processStyleName(styleName) + ':'; + serialized += dangerousStyleValue(styleName, styleValue) + ';'; + } + } + return serialized || null; + }, + + /** + * Sets the value for multiple styles on a node. If a value is specified as + * '' (empty string), the corresponding style property will be unset. + * + * @param {DOMElement} node + * @param {object} styles + */ + setValueForStyles: function(node, styles) { + var style = node.style; + for (var styleName in styles) { + if (!styles.hasOwnProperty(styleName)) { + continue; + } + if ("production" !== process.env.NODE_ENV) { + warnValidStyle(styleName, styles[styleName]); + } + var styleValue = dangerousStyleValue(styleName, styles[styleName]); + if (styleName === 'float') { + styleName = styleFloatAccessor; + } + if (styleValue) { + style[styleName] = styleValue; + } else { + var expansion = CSSProperty.shorthandPropertyExpansions[styleName]; + if (expansion) { + // Shorthand property that IE8 won't like unsetting, so unset each + // component to placate it + for (var individualStyleName in expansion) { + style[individualStyleName] = ''; + } + } else { + style[styleName] = ''; + } + } + } + } + +}; + +module.exports = CSSPropertyOperations; + +}).call(this,require('_process')) + +},{"./CSSProperty":5,"./ExecutionEnvironment":22,"./camelizeStyleName":122,"./dangerousStyleValue":129,"./hyphenateStyleName":149,"./memoizeStringOnly":160,"./warning":172,"_process":174}],7:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule CallbackQueue + */ + +'use strict'; + +var PooledClass = require("./PooledClass"); + +var assign = require("./Object.assign"); +var invariant = require("./invariant"); + +/** + * A specialized pseudo-event module to help keep track of components waiting to + * be notified when their DOM representations are available for use. + * + * This implements `PooledClass`, so you should never need to instantiate this. + * Instead, use `CallbackQueue.getPooled()`. + * + * @class ReactMountReady + * @implements PooledClass + * @internal + */ +function CallbackQueue() { + this._callbacks = null; + this._contexts = null; +} + +assign(CallbackQueue.prototype, { + + /** + * Enqueues a callback to be invoked when `notifyAll` is invoked. + * + * @param {function} callback Invoked when `notifyAll` is invoked. + * @param {?object} context Context to call `callback` with. + * @internal + */ + enqueue: function(callback, context) { + this._callbacks = this._callbacks || []; + this._contexts = this._contexts || []; + this._callbacks.push(callback); + this._contexts.push(context); + }, + + /** + * Invokes all enqueued callbacks and clears the queue. This is invoked after + * the DOM representation of a component has been created or updated. + * + * @internal + */ + notifyAll: function() { + var callbacks = this._callbacks; + var contexts = this._contexts; + if (callbacks) { + ("production" !== process.env.NODE_ENV ? invariant( + callbacks.length === contexts.length, + 'Mismatched list of contexts in callback queue' + ) : invariant(callbacks.length === contexts.length)); + this._callbacks = null; + this._contexts = null; + for (var i = 0, l = callbacks.length; i < l; i++) { + callbacks[i].call(contexts[i]); + } + callbacks.length = 0; + contexts.length = 0; + } + }, + + /** + * Resets the internal queue. + * + * @internal + */ + reset: function() { + this._callbacks = null; + this._contexts = null; + }, + + /** + * `PooledClass` looks for this. + */ + destructor: function() { + this.reset(); + } + +}); + +PooledClass.addPoolingTo(CallbackQueue); + +module.exports = CallbackQueue; + +}).call(this,require('_process')) + +},{"./Object.assign":29,"./PooledClass":30,"./invariant":151,"_process":174}],8:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ChangeEventPlugin + */ + +'use strict'; + +var EventConstants = require("./EventConstants"); +var EventPluginHub = require("./EventPluginHub"); +var EventPropagators = require("./EventPropagators"); +var ExecutionEnvironment = require("./ExecutionEnvironment"); +var ReactUpdates = require("./ReactUpdates"); +var SyntheticEvent = require("./SyntheticEvent"); + +var isEventSupported = require("./isEventSupported"); +var isTextInputElement = require("./isTextInputElement"); +var keyOf = require("./keyOf"); + +var topLevelTypes = EventConstants.topLevelTypes; + +var eventTypes = { + change: { + phasedRegistrationNames: { + bubbled: keyOf({onChange: null}), + captured: keyOf({onChangeCapture: null}) + }, + dependencies: [ + topLevelTypes.topBlur, + topLevelTypes.topChange, + topLevelTypes.topClick, + topLevelTypes.topFocus, + topLevelTypes.topInput, + topLevelTypes.topKeyDown, + topLevelTypes.topKeyUp, + topLevelTypes.topSelectionChange + ] + } +}; + +/** + * For IE shims + */ +var activeElement = null; +var activeElementID = null; +var activeElementValue = null; +var activeElementValueProp = null; + +/** + * SECTION: handle `change` event + */ +function shouldUseChangeEvent(elem) { + return ( + elem.nodeName === 'SELECT' || + (elem.nodeName === 'INPUT' && elem.type === 'file') + ); +} + +var doesChangeEventBubble = false; +if (ExecutionEnvironment.canUseDOM) { + // See `handleChange` comment below + doesChangeEventBubble = isEventSupported('change') && ( + (!('documentMode' in document) || document.documentMode > 8) + ); +} + +function manualDispatchChangeEvent(nativeEvent) { + var event = SyntheticEvent.getPooled( + eventTypes.change, + activeElementID, + nativeEvent + ); + EventPropagators.accumulateTwoPhaseDispatches(event); + + // If change and propertychange bubbled, we'd just bind to it like all the + // other events and have it go through ReactBrowserEventEmitter. Since it + // doesn't, we manually listen for the events and so we have to enqueue and + // process the abstract event manually. + // + // Batching is necessary here in order to ensure that all event handlers run + // before the next rerender (including event handlers attached to ancestor + // elements instead of directly on the input). Without this, controlled + // components don't work properly in conjunction with event bubbling because + // the component is rerendered and the value reverted before all the event + // handlers can run. See https://github.com/facebook/react/issues/708. + ReactUpdates.batchedUpdates(runEventInBatch, event); +} + +function runEventInBatch(event) { + EventPluginHub.enqueueEvents(event); + EventPluginHub.processEventQueue(); +} + +function startWatchingForChangeEventIE8(target, targetID) { + activeElement = target; + activeElementID = targetID; + activeElement.attachEvent('onchange', manualDispatchChangeEvent); +} + +function stopWatchingForChangeEventIE8() { + if (!activeElement) { + return; + } + activeElement.detachEvent('onchange', manualDispatchChangeEvent); + activeElement = null; + activeElementID = null; +} + +function getTargetIDForChangeEvent( + topLevelType, + topLevelTarget, + topLevelTargetID) { + if (topLevelType === topLevelTypes.topChange) { + return topLevelTargetID; + } +} +function handleEventsForChangeEventIE8( + topLevelType, + topLevelTarget, + topLevelTargetID) { + if (topLevelType === topLevelTypes.topFocus) { + // stopWatching() should be a noop here but we call it just in case we + // missed a blur event somehow. + stopWatchingForChangeEventIE8(); + startWatchingForChangeEventIE8(topLevelTarget, topLevelTargetID); + } else if (topLevelType === topLevelTypes.topBlur) { + stopWatchingForChangeEventIE8(); + } +} + + +/** + * SECTION: handle `input` event + */ +var isInputEventSupported = false; +if (ExecutionEnvironment.canUseDOM) { + // IE9 claims to support the input event but fails to trigger it when + // deleting text, so we ignore its input events + isInputEventSupported = isEventSupported('input') && ( + (!('documentMode' in document) || document.documentMode > 9) + ); +} + +/** + * (For old IE.) Replacement getter/setter for the `value` property that gets + * set on the active element. + */ +var newValueProp = { + get: function() { + return activeElementValueProp.get.call(this); + }, + set: function(val) { + // Cast to a string so we can do equality checks. + activeElementValue = '' + val; + activeElementValueProp.set.call(this, val); + } +}; + +/** + * (For old IE.) Starts tracking propertychange events on the passed-in element + * and override the value property so that we can distinguish user events from + * value changes in JS. + */ +function startWatchingForValueChange(target, targetID) { + activeElement = target; + activeElementID = targetID; + activeElementValue = target.value; + activeElementValueProp = Object.getOwnPropertyDescriptor( + target.constructor.prototype, + 'value' + ); + + Object.defineProperty(activeElement, 'value', newValueProp); + activeElement.attachEvent('onpropertychange', handlePropertyChange); +} + +/** + * (For old IE.) Removes the event listeners from the currently-tracked element, + * if any exists. + */ +function stopWatchingForValueChange() { + if (!activeElement) { + return; + } + + // delete restores the original property definition + delete activeElement.value; + activeElement.detachEvent('onpropertychange', handlePropertyChange); + + activeElement = null; + activeElementID = null; + activeElementValue = null; + activeElementValueProp = null; +} + +/** + * (For old IE.) Handles a propertychange event, sending a `change` event if + * the value of the active element has changed. + */ +function handlePropertyChange(nativeEvent) { + if (nativeEvent.propertyName !== 'value') { + return; + } + var value = nativeEvent.srcElement.value; + if (value === activeElementValue) { + return; + } + activeElementValue = value; + + manualDispatchChangeEvent(nativeEvent); +} + +/** + * If a `change` event should be fired, returns the target's ID. + */ +function getTargetIDForInputEvent( + topLevelType, + topLevelTarget, + topLevelTargetID) { + if (topLevelType === topLevelTypes.topInput) { + // In modern browsers (i.e., not IE8 or IE9), the input event is exactly + // what we want so fall through here and trigger an abstract event + return topLevelTargetID; + } +} + +// For IE8 and IE9. +function handleEventsForInputEventIE( + topLevelType, + topLevelTarget, + topLevelTargetID) { + if (topLevelType === topLevelTypes.topFocus) { + // In IE8, we can capture almost all .value changes by adding a + // propertychange handler and looking for events with propertyName + // equal to 'value' + // In IE9, propertychange fires for most input events but is buggy and + // doesn't fire when text is deleted, but conveniently, selectionchange + // appears to fire in all of the remaining cases so we catch those and + // forward the event if the value has changed + // In either case, we don't want to call the event handler if the value + // is changed from JS so we redefine a setter for `.value` that updates + // our activeElementValue variable, allowing us to ignore those changes + // + // stopWatching() should be a noop here but we call it just in case we + // missed a blur event somehow. + stopWatchingForValueChange(); + startWatchingForValueChange(topLevelTarget, topLevelTargetID); + } else if (topLevelType === topLevelTypes.topBlur) { + stopWatchingForValueChange(); + } +} + +// For IE8 and IE9. +function getTargetIDForInputEventIE( + topLevelType, + topLevelTarget, + topLevelTargetID) { + if (topLevelType === topLevelTypes.topSelectionChange || + topLevelType === topLevelTypes.topKeyUp || + topLevelType === topLevelTypes.topKeyDown) { + // On the selectionchange event, the target is just document which isn't + // helpful for us so just check activeElement instead. + // + // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire + // propertychange on the first input event after setting `value` from a + // script and fires only keydown, keypress, keyup. Catching keyup usually + // gets it and catching keydown lets us fire an event for the first + // keystroke if user does a key repeat (it'll be a little delayed: right + // before the second keystroke). Other input methods (e.g., paste) seem to + // fire selectionchange normally. + if (activeElement && activeElement.value !== activeElementValue) { + activeElementValue = activeElement.value; + return activeElementID; + } + } +} + + +/** + * SECTION: handle `click` event + */ +function shouldUseClickEvent(elem) { + // Use the `click` event to detect changes to checkbox and radio inputs. + // This approach works across all browsers, whereas `change` does not fire + // until `blur` in IE8. + return ( + elem.nodeName === 'INPUT' && + (elem.type === 'checkbox' || elem.type === 'radio') + ); +} + +function getTargetIDForClickEvent( + topLevelType, + topLevelTarget, + topLevelTargetID) { + if (topLevelType === topLevelTypes.topClick) { + return topLevelTargetID; + } +} + +/** + * This plugin creates an `onChange` event that normalizes change events + * across form elements. This event fires at a time when it's possible to + * change the element's value without seeing a flicker. + * + * Supported elements are: + * - input (see `isTextInputElement`) + * - textarea + * - select + */ +var ChangeEventPlugin = { + + eventTypes: eventTypes, + + /** + * @param {string} topLevelType Record from `EventConstants`. + * @param {DOMEventTarget} topLevelTarget The listening component root node. + * @param {string} topLevelTargetID ID of `topLevelTarget`. + * @param {object} nativeEvent Native browser event. + * @return {*} An accumulation of synthetic events. + * @see {EventPluginHub.extractEvents} + */ + extractEvents: function( + topLevelType, + topLevelTarget, + topLevelTargetID, + nativeEvent) { + + var getTargetIDFunc, handleEventFunc; + if (shouldUseChangeEvent(topLevelTarget)) { + if (doesChangeEventBubble) { + getTargetIDFunc = getTargetIDForChangeEvent; + } else { + handleEventFunc = handleEventsForChangeEventIE8; + } + } else if (isTextInputElement(topLevelTarget)) { + if (isInputEventSupported) { + getTargetIDFunc = getTargetIDForInputEvent; + } else { + getTargetIDFunc = getTargetIDForInputEventIE; + handleEventFunc = handleEventsForInputEventIE; + } + } else if (shouldUseClickEvent(topLevelTarget)) { + getTargetIDFunc = getTargetIDForClickEvent; + } + + if (getTargetIDFunc) { + var targetID = getTargetIDFunc( + topLevelType, + topLevelTarget, + topLevelTargetID + ); + if (targetID) { + var event = SyntheticEvent.getPooled( + eventTypes.change, + targetID, + nativeEvent + ); + EventPropagators.accumulateTwoPhaseDispatches(event); + return event; + } + } + + if (handleEventFunc) { + handleEventFunc( + topLevelType, + topLevelTarget, + topLevelTargetID + ); + } + } + +}; + +module.exports = ChangeEventPlugin; + +},{"./EventConstants":16,"./EventPluginHub":18,"./EventPropagators":21,"./ExecutionEnvironment":22,"./ReactUpdates":100,"./SyntheticEvent":109,"./isEventSupported":152,"./isTextInputElement":154,"./keyOf":158}],9:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ClientReactRootIndex + * @typechecks + */ + +'use strict'; + +var nextReactRootIndex = 0; + +var ClientReactRootIndex = { + createReactRootIndex: function() { + return nextReactRootIndex++; + } +}; + +module.exports = ClientReactRootIndex; + +},{}],10:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DOMChildrenOperations + * @typechecks static-only + */ + +'use strict'; + +var Danger = require("./Danger"); +var ReactMultiChildUpdateTypes = require("./ReactMultiChildUpdateTypes"); + +var setTextContent = require("./setTextContent"); +var invariant = require("./invariant"); + +/** + * Inserts `childNode` as a child of `parentNode` at the `index`. + * + * @param {DOMElement} parentNode Parent node in which to insert. + * @param {DOMElement} childNode Child node to insert. + * @param {number} index Index at which to insert the child. + * @internal + */ +function insertChildAt(parentNode, childNode, index) { + // By exploiting arrays returning `undefined` for an undefined index, we can + // rely exclusively on `insertBefore(node, null)` instead of also using + // `appendChild(node)`. However, using `undefined` is not allowed by all + // browsers so we must replace it with `null`. + parentNode.insertBefore( + childNode, + parentNode.childNodes[index] || null + ); +} + +/** + * Operations for updating with DOM children. + */ +var DOMChildrenOperations = { + + dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup, + + updateTextContent: setTextContent, + + /** + * Updates a component's children by processing a series of updates. The + * update configurations are each expected to have a `parentNode` property. + * + * @param {array} updates List of update configurations. + * @param {array} markupList List of markup strings. + * @internal + */ + processUpdates: function(updates, markupList) { + var update; + // Mapping from parent IDs to initial child orderings. + var initialChildren = null; + // List of children that will be moved or removed. + var updatedChildren = null; + + for (var i = 0; i < updates.length; i++) { + update = updates[i]; + if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING || + update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) { + var updatedIndex = update.fromIndex; + var updatedChild = update.parentNode.childNodes[updatedIndex]; + var parentID = update.parentID; + + ("production" !== process.env.NODE_ENV ? invariant( + updatedChild, + 'processUpdates(): Unable to find child %s of element. This ' + + 'probably means the DOM was unexpectedly mutated (e.g., by the ' + + 'browser), usually due to forgetting a when using tables, ' + + 'nesting tags like
,

, or , or using non-SVG elements ' + + 'in an parent. Try inspecting the child nodes of the element ' + + 'with React ID `%s`.', + updatedIndex, + parentID + ) : invariant(updatedChild)); + + initialChildren = initialChildren || {}; + initialChildren[parentID] = initialChildren[parentID] || []; + initialChildren[parentID][updatedIndex] = updatedChild; + + updatedChildren = updatedChildren || []; + updatedChildren.push(updatedChild); + } + } + + var renderedMarkup = Danger.dangerouslyRenderMarkup(markupList); + + // Remove updated children first so that `toIndex` is consistent. + if (updatedChildren) { + for (var j = 0; j < updatedChildren.length; j++) { + updatedChildren[j].parentNode.removeChild(updatedChildren[j]); + } + } + + for (var k = 0; k < updates.length; k++) { + update = updates[k]; + switch (update.type) { + case ReactMultiChildUpdateTypes.INSERT_MARKUP: + insertChildAt( + update.parentNode, + renderedMarkup[update.markupIndex], + update.toIndex + ); + break; + case ReactMultiChildUpdateTypes.MOVE_EXISTING: + insertChildAt( + update.parentNode, + initialChildren[update.parentID][update.fromIndex], + update.toIndex + ); + break; + case ReactMultiChildUpdateTypes.TEXT_CONTENT: + setTextContent( + update.parentNode, + update.textContent + ); + break; + case ReactMultiChildUpdateTypes.REMOVE_NODE: + // Already removed by the for-loop above. + break; + } + } + } + +}; + +module.exports = DOMChildrenOperations; + +}).call(this,require('_process')) + +},{"./Danger":13,"./ReactMultiChildUpdateTypes":79,"./invariant":151,"./setTextContent":166,"_process":174}],11:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DOMProperty + * @typechecks static-only + */ + +/*jslint bitwise: true */ + +'use strict'; + +var invariant = require("./invariant"); + +function checkMask(value, bitmask) { + return (value & bitmask) === bitmask; +} + +var DOMPropertyInjection = { + /** + * Mapping from normalized, camelcased property names to a configuration that + * specifies how the associated DOM property should be accessed or rendered. + */ + MUST_USE_ATTRIBUTE: 0x1, + MUST_USE_PROPERTY: 0x2, + HAS_SIDE_EFFECTS: 0x4, + HAS_BOOLEAN_VALUE: 0x8, + HAS_NUMERIC_VALUE: 0x10, + HAS_POSITIVE_NUMERIC_VALUE: 0x20 | 0x10, + HAS_OVERLOADED_BOOLEAN_VALUE: 0x40, + + /** + * Inject some specialized knowledge about the DOM. This takes a config object + * with the following properties: + * + * isCustomAttribute: function that given an attribute name will return true + * if it can be inserted into the DOM verbatim. Useful for data-* or aria-* + * attributes where it's impossible to enumerate all of the possible + * attribute names, + * + * Properties: object mapping DOM property name to one of the + * DOMPropertyInjection constants or null. If your attribute isn't in here, + * it won't get written to the DOM. + * + * DOMAttributeNames: object mapping React attribute name to the DOM + * attribute name. Attribute names not specified use the **lowercase** + * normalized name. + * + * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties. + * Property names not specified use the normalized name. + * + * DOMMutationMethods: Properties that require special mutation methods. If + * `value` is undefined, the mutation method should unset the property. + * + * @param {object} domPropertyConfig the config as described above. + */ + injectDOMPropertyConfig: function(domPropertyConfig) { + var Properties = domPropertyConfig.Properties || {}; + var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {}; + var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {}; + var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {}; + + if (domPropertyConfig.isCustomAttribute) { + DOMProperty._isCustomAttributeFunctions.push( + domPropertyConfig.isCustomAttribute + ); + } + + for (var propName in Properties) { + ("production" !== process.env.NODE_ENV ? invariant( + !DOMProperty.isStandardName.hasOwnProperty(propName), + 'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' + + '\'%s\' which has already been injected. You may be accidentally ' + + 'injecting the same DOM property config twice, or you may be ' + + 'injecting two configs that have conflicting property names.', + propName + ) : invariant(!DOMProperty.isStandardName.hasOwnProperty(propName))); + + DOMProperty.isStandardName[propName] = true; + + var lowerCased = propName.toLowerCase(); + DOMProperty.getPossibleStandardName[lowerCased] = propName; + + if (DOMAttributeNames.hasOwnProperty(propName)) { + var attributeName = DOMAttributeNames[propName]; + DOMProperty.getPossibleStandardName[attributeName] = propName; + DOMProperty.getAttributeName[propName] = attributeName; + } else { + DOMProperty.getAttributeName[propName] = lowerCased; + } + + DOMProperty.getPropertyName[propName] = + DOMPropertyNames.hasOwnProperty(propName) ? + DOMPropertyNames[propName] : + propName; + + if (DOMMutationMethods.hasOwnProperty(propName)) { + DOMProperty.getMutationMethod[propName] = DOMMutationMethods[propName]; + } else { + DOMProperty.getMutationMethod[propName] = null; + } + + var propConfig = Properties[propName]; + DOMProperty.mustUseAttribute[propName] = + checkMask(propConfig, DOMPropertyInjection.MUST_USE_ATTRIBUTE); + DOMProperty.mustUseProperty[propName] = + checkMask(propConfig, DOMPropertyInjection.MUST_USE_PROPERTY); + DOMProperty.hasSideEffects[propName] = + checkMask(propConfig, DOMPropertyInjection.HAS_SIDE_EFFECTS); + DOMProperty.hasBooleanValue[propName] = + checkMask(propConfig, DOMPropertyInjection.HAS_BOOLEAN_VALUE); + DOMProperty.hasNumericValue[propName] = + checkMask(propConfig, DOMPropertyInjection.HAS_NUMERIC_VALUE); + DOMProperty.hasPositiveNumericValue[propName] = + checkMask(propConfig, DOMPropertyInjection.HAS_POSITIVE_NUMERIC_VALUE); + DOMProperty.hasOverloadedBooleanValue[propName] = + checkMask(propConfig, DOMPropertyInjection.HAS_OVERLOADED_BOOLEAN_VALUE); + + ("production" !== process.env.NODE_ENV ? invariant( + !DOMProperty.mustUseAttribute[propName] || + !DOMProperty.mustUseProperty[propName], + 'DOMProperty: Cannot require using both attribute and property: %s', + propName + ) : invariant(!DOMProperty.mustUseAttribute[propName] || + !DOMProperty.mustUseProperty[propName])); + ("production" !== process.env.NODE_ENV ? invariant( + DOMProperty.mustUseProperty[propName] || + !DOMProperty.hasSideEffects[propName], + 'DOMProperty: Properties that have side effects must use property: %s', + propName + ) : invariant(DOMProperty.mustUseProperty[propName] || + !DOMProperty.hasSideEffects[propName])); + ("production" !== process.env.NODE_ENV ? invariant( + !!DOMProperty.hasBooleanValue[propName] + + !!DOMProperty.hasNumericValue[propName] + + !!DOMProperty.hasOverloadedBooleanValue[propName] <= 1, + 'DOMProperty: Value can be one of boolean, overloaded boolean, or ' + + 'numeric value, but not a combination: %s', + propName + ) : invariant(!!DOMProperty.hasBooleanValue[propName] + + !!DOMProperty.hasNumericValue[propName] + + !!DOMProperty.hasOverloadedBooleanValue[propName] <= 1)); + } + } +}; +var defaultValueCache = {}; + +/** + * DOMProperty exports lookup objects that can be used like functions: + * + * > DOMProperty.isValid['id'] + * true + * > DOMProperty.isValid['foobar'] + * undefined + * + * Although this may be confusing, it performs better in general. + * + * @see http://jsperf.com/key-exists + * @see http://jsperf.com/key-missing + */ +var DOMProperty = { + + ID_ATTRIBUTE_NAME: 'data-reactid', + + /** + * Checks whether a property name is a standard property. + * @type {Object} + */ + isStandardName: {}, + + /** + * Mapping from lowercase property names to the properly cased version, used + * to warn in the case of missing properties. + * @type {Object} + */ + getPossibleStandardName: {}, + + /** + * Mapping from normalized names to attribute names that differ. Attribute + * names are used when rendering markup or with `*Attribute()`. + * @type {Object} + */ + getAttributeName: {}, + + /** + * Mapping from normalized names to properties on DOM node instances. + * (This includes properties that mutate due to external factors.) + * @type {Object} + */ + getPropertyName: {}, + + /** + * Mapping from normalized names to mutation methods. This will only exist if + * mutation cannot be set simply by the property or `setAttribute()`. + * @type {Object} + */ + getMutationMethod: {}, + + /** + * Whether the property must be accessed and mutated as an object property. + * @type {Object} + */ + mustUseAttribute: {}, + + /** + * Whether the property must be accessed and mutated using `*Attribute()`. + * (This includes anything that fails ` in `.) + * @type {Object} + */ + mustUseProperty: {}, + + /** + * Whether or not setting a value causes side effects such as triggering + * resources to be loaded or text selection changes. We must ensure that + * the value is only set if it has changed. + * @type {Object} + */ + hasSideEffects: {}, + + /** + * Whether the property should be removed when set to a falsey value. + * @type {Object} + */ + hasBooleanValue: {}, + + /** + * Whether the property must be numeric or parse as a + * numeric and should be removed when set to a falsey value. + * @type {Object} + */ + hasNumericValue: {}, + + /** + * Whether the property must be positive numeric or parse as a positive + * numeric and should be removed when set to a falsey value. + * @type {Object} + */ + hasPositiveNumericValue: {}, + + /** + * Whether the property can be used as a flag as well as with a value. Removed + * when strictly equal to false; present without a value when strictly equal + * to true; present with a value otherwise. + * @type {Object} + */ + hasOverloadedBooleanValue: {}, + + /** + * All of the isCustomAttribute() functions that have been injected. + */ + _isCustomAttributeFunctions: [], + + /** + * Checks whether a property name is a custom attribute. + * @method + */ + isCustomAttribute: function(attributeName) { + for (var i = 0; i < DOMProperty._isCustomAttributeFunctions.length; i++) { + var isCustomAttributeFn = DOMProperty._isCustomAttributeFunctions[i]; + if (isCustomAttributeFn(attributeName)) { + return true; + } + } + return false; + }, + + /** + * Returns the default property value for a DOM property (i.e., not an + * attribute). Most default values are '' or false, but not all. Worse yet, + * some (in particular, `type`) vary depending on the type of element. + * + * TODO: Is it better to grab all the possible properties when creating an + * element to avoid having to create the same element twice? + */ + getDefaultValueForProperty: function(nodeName, prop) { + var nodeDefaults = defaultValueCache[nodeName]; + var testElement; + if (!nodeDefaults) { + defaultValueCache[nodeName] = nodeDefaults = {}; + } + if (!(prop in nodeDefaults)) { + testElement = document.createElement(nodeName); + nodeDefaults[prop] = testElement[prop]; + } + return nodeDefaults[prop]; + }, + + injection: DOMPropertyInjection +}; + +module.exports = DOMProperty; + +}).call(this,require('_process')) + +},{"./invariant":151,"_process":174}],12:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DOMPropertyOperations + * @typechecks static-only + */ + +'use strict'; + +var DOMProperty = require("./DOMProperty"); + +var quoteAttributeValueForBrowser = require("./quoteAttributeValueForBrowser"); +var warning = require("./warning"); + +function shouldIgnoreValue(name, value) { + return value == null || + (DOMProperty.hasBooleanValue[name] && !value) || + (DOMProperty.hasNumericValue[name] && isNaN(value)) || + (DOMProperty.hasPositiveNumericValue[name] && (value < 1)) || + (DOMProperty.hasOverloadedBooleanValue[name] && value === false); +} + +if ("production" !== process.env.NODE_ENV) { + var reactProps = { + children: true, + dangerouslySetInnerHTML: true, + key: true, + ref: true + }; + var warnedProperties = {}; + + var warnUnknownProperty = function(name) { + if (reactProps.hasOwnProperty(name) && reactProps[name] || + warnedProperties.hasOwnProperty(name) && warnedProperties[name]) { + return; + } + + warnedProperties[name] = true; + var lowerCasedName = name.toLowerCase(); + + // data-* attributes should be lowercase; suggest the lowercase version + var standardName = ( + DOMProperty.isCustomAttribute(lowerCasedName) ? + lowerCasedName : + DOMProperty.getPossibleStandardName.hasOwnProperty(lowerCasedName) ? + DOMProperty.getPossibleStandardName[lowerCasedName] : + null + ); + + // For now, only warn when we have a suggested correction. This prevents + // logging too much when using transferPropsTo. + ("production" !== process.env.NODE_ENV ? warning( + standardName == null, + 'Unknown DOM property %s. Did you mean %s?', + name, + standardName + ) : null); + + }; +} + +/** + * Operations for dealing with DOM properties. + */ +var DOMPropertyOperations = { + + /** + * Creates markup for the ID property. + * + * @param {string} id Unescaped ID. + * @return {string} Markup string. + */ + createMarkupForID: function(id) { + return DOMProperty.ID_ATTRIBUTE_NAME + '=' + + quoteAttributeValueForBrowser(id); + }, + + /** + * Creates markup for a property. + * + * @param {string} name + * @param {*} value + * @return {?string} Markup string, or null if the property was invalid. + */ + createMarkupForProperty: function(name, value) { + if (DOMProperty.isStandardName.hasOwnProperty(name) && + DOMProperty.isStandardName[name]) { + if (shouldIgnoreValue(name, value)) { + return ''; + } + var attributeName = DOMProperty.getAttributeName[name]; + if (DOMProperty.hasBooleanValue[name] || + (DOMProperty.hasOverloadedBooleanValue[name] && value === true)) { + return attributeName; + } + return attributeName + '=' + quoteAttributeValueForBrowser(value); + } else if (DOMProperty.isCustomAttribute(name)) { + if (value == null) { + return ''; + } + return name + '=' + quoteAttributeValueForBrowser(value); + } else if ("production" !== process.env.NODE_ENV) { + warnUnknownProperty(name); + } + return null; + }, + + /** + * Sets the value for a property on a node. + * + * @param {DOMElement} node + * @param {string} name + * @param {*} value + */ + setValueForProperty: function(node, name, value) { + if (DOMProperty.isStandardName.hasOwnProperty(name) && + DOMProperty.isStandardName[name]) { + var mutationMethod = DOMProperty.getMutationMethod[name]; + if (mutationMethod) { + mutationMethod(node, value); + } else if (shouldIgnoreValue(name, value)) { + this.deleteValueForProperty(node, name); + } else if (DOMProperty.mustUseAttribute[name]) { + // `setAttribute` with objects becomes only `[object]` in IE8/9, + // ('' + value) makes it output the correct toString()-value. + node.setAttribute(DOMProperty.getAttributeName[name], '' + value); + } else { + var propName = DOMProperty.getPropertyName[name]; + // Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the + // property type before comparing; only `value` does and is string. + if (!DOMProperty.hasSideEffects[name] || + ('' + node[propName]) !== ('' + value)) { + // Contrary to `setAttribute`, object properties are properly + // `toString`ed by IE8/9. + node[propName] = value; + } + } + } else if (DOMProperty.isCustomAttribute(name)) { + if (value == null) { + node.removeAttribute(name); + } else { + node.setAttribute(name, '' + value); + } + } else if ("production" !== process.env.NODE_ENV) { + warnUnknownProperty(name); + } + }, + + /** + * Deletes the value for a property on a node. + * + * @param {DOMElement} node + * @param {string} name + */ + deleteValueForProperty: function(node, name) { + if (DOMProperty.isStandardName.hasOwnProperty(name) && + DOMProperty.isStandardName[name]) { + var mutationMethod = DOMProperty.getMutationMethod[name]; + if (mutationMethod) { + mutationMethod(node, undefined); + } else if (DOMProperty.mustUseAttribute[name]) { + node.removeAttribute(DOMProperty.getAttributeName[name]); + } else { + var propName = DOMProperty.getPropertyName[name]; + var defaultValue = DOMProperty.getDefaultValueForProperty( + node.nodeName, + propName + ); + if (!DOMProperty.hasSideEffects[name] || + ('' + node[propName]) !== defaultValue) { + node[propName] = defaultValue; + } + } + } else if (DOMProperty.isCustomAttribute(name)) { + node.removeAttribute(name); + } else if ("production" !== process.env.NODE_ENV) { + warnUnknownProperty(name); + } + } + +}; + +module.exports = DOMPropertyOperations; + +}).call(this,require('_process')) + +},{"./DOMProperty":11,"./quoteAttributeValueForBrowser":164,"./warning":172,"_process":174}],13:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Danger + * @typechecks static-only + */ + +/*jslint evil: true, sub: true */ + +'use strict'; + +var ExecutionEnvironment = require("./ExecutionEnvironment"); + +var createNodesFromMarkup = require("./createNodesFromMarkup"); +var emptyFunction = require("./emptyFunction"); +var getMarkupWrap = require("./getMarkupWrap"); +var invariant = require("./invariant"); + +var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/; +var RESULT_INDEX_ATTR = 'data-danger-index'; + +/** + * Extracts the `nodeName` from a string of markup. + * + * NOTE: Extracting the `nodeName` does not require a regular expression match + * because we make assumptions about React-generated markup (i.e. there are no + * spaces surrounding the opening tag and there is at least one attribute). + * + * @param {string} markup String of markup. + * @return {string} Node name of the supplied markup. + * @see http://jsperf.com/extract-nodename + */ +function getNodeName(markup) { + return markup.substring(1, markup.indexOf(' ')); +} + +var Danger = { + + /** + * Renders markup into an array of nodes. The markup is expected to render + * into a list of root nodes. Also, the length of `resultList` and + * `markupList` should be the same. + * + * @param {array} markupList List of markup strings to render. + * @return {array} List of rendered nodes. + * @internal + */ + dangerouslyRenderMarkup: function(markupList) { + ("production" !== process.env.NODE_ENV ? invariant( + ExecutionEnvironment.canUseDOM, + 'dangerouslyRenderMarkup(...): Cannot render markup in a worker ' + + 'thread. Make sure `window` and `document` are available globally ' + + 'before requiring React when unit testing or use ' + + 'React.renderToString for server rendering.' + ) : invariant(ExecutionEnvironment.canUseDOM)); + var nodeName; + var markupByNodeName = {}; + // Group markup by `nodeName` if a wrap is necessary, else by '*'. + for (var i = 0; i < markupList.length; i++) { + ("production" !== process.env.NODE_ENV ? invariant( + markupList[i], + 'dangerouslyRenderMarkup(...): Missing markup.' + ) : invariant(markupList[i])); + nodeName = getNodeName(markupList[i]); + nodeName = getMarkupWrap(nodeName) ? nodeName : '*'; + markupByNodeName[nodeName] = markupByNodeName[nodeName] || []; + markupByNodeName[nodeName][i] = markupList[i]; + } + var resultList = []; + var resultListAssignmentCount = 0; + for (nodeName in markupByNodeName) { + if (!markupByNodeName.hasOwnProperty(nodeName)) { + continue; + } + var markupListByNodeName = markupByNodeName[nodeName]; + + // This for-in loop skips the holes of the sparse array. The order of + // iteration should follow the order of assignment, which happens to match + // numerical index order, but we don't rely on that. + var resultIndex; + for (resultIndex in markupListByNodeName) { + if (markupListByNodeName.hasOwnProperty(resultIndex)) { + var markup = markupListByNodeName[resultIndex]; + + // Push the requested markup with an additional RESULT_INDEX_ATTR + // attribute. If the markup does not start with a < character, it + // will be discarded below (with an appropriate console.error). + markupListByNodeName[resultIndex] = markup.replace( + OPEN_TAG_NAME_EXP, + // This index will be parsed back out below. + '$1 ' + RESULT_INDEX_ATTR + '="' + resultIndex + '" ' + ); + } + } + + // Render each group of markup with similar wrapping `nodeName`. + var renderNodes = createNodesFromMarkup( + markupListByNodeName.join(''), + emptyFunction // Do nothing special with

; + * } + * }); + * + * The class specification supports a specific protocol of methods that have + * special meaning (e.g. `render`). See `ReactClassInterface` for + * more the comprehensive protocol. Any other properties and methods in the + * class specification will available on the prototype. + * + * @interface ReactClassInterface + * @internal + */ +var ReactClassInterface = { + + /** + * An array of Mixin objects to include when defining your component. + * + * @type {array} + * @optional + */ + mixins: SpecPolicy.DEFINE_MANY, + + /** + * An object containing properties and methods that should be defined on + * the component's constructor instead of its prototype (static methods). + * + * @type {object} + * @optional + */ + statics: SpecPolicy.DEFINE_MANY, + + /** + * Definition of prop types for this component. + * + * @type {object} + * @optional + */ + propTypes: SpecPolicy.DEFINE_MANY, + + /** + * Definition of context types for this component. + * + * @type {object} + * @optional + */ + contextTypes: SpecPolicy.DEFINE_MANY, + + /** + * Definition of context types this component sets for its children. + * + * @type {object} + * @optional + */ + childContextTypes: SpecPolicy.DEFINE_MANY, + + // ==== Definition methods ==== + + /** + * Invoked when the component is mounted. Values in the mapping will be set on + * `this.props` if that prop is not specified (i.e. using an `in` check). + * + * This method is invoked before `getInitialState` and therefore cannot rely + * on `this.state` or use `this.setState`. + * + * @return {object} + * @optional + */ + getDefaultProps: SpecPolicy.DEFINE_MANY_MERGED, + + /** + * Invoked once before the component is mounted. The return value will be used + * as the initial value of `this.state`. + * + * getInitialState: function() { + * return { + * isOn: false, + * fooBaz: new BazFoo() + * } + * } + * + * @return {object} + * @optional + */ + getInitialState: SpecPolicy.DEFINE_MANY_MERGED, + + /** + * @return {object} + * @optional + */ + getChildContext: SpecPolicy.DEFINE_MANY_MERGED, + + /** + * Uses props from `this.props` and state from `this.state` to render the + * structure of the component. + * + * No guarantees are made about when or how often this method is invoked, so + * it must not have side effects. + * + * render: function() { + * var name = this.props.name; + * return
Hello, {name}!
; + * } + * + * @return {ReactComponent} + * @nosideeffects + * @required + */ + render: SpecPolicy.DEFINE_ONCE, + + + + // ==== Delegate methods ==== + + /** + * Invoked when the component is initially created and about to be mounted. + * This may have side effects, but any external subscriptions or data created + * by this method must be cleaned up in `componentWillUnmount`. + * + * @optional + */ + componentWillMount: SpecPolicy.DEFINE_MANY, + + /** + * Invoked when the component has been mounted and has a DOM representation. + * However, there is no guarantee that the DOM node is in the document. + * + * Use this as an opportunity to operate on the DOM when the component has + * been mounted (initialized and rendered) for the first time. + * + * @param {DOMElement} rootNode DOM element representing the component. + * @optional + */ + componentDidMount: SpecPolicy.DEFINE_MANY, + + /** + * Invoked before the component receives new props. + * + * Use this as an opportunity to react to a prop transition by updating the + * state using `this.setState`. Current props are accessed via `this.props`. + * + * componentWillReceiveProps: function(nextProps, nextContext) { + * this.setState({ + * likesIncreasing: nextProps.likeCount > this.props.likeCount + * }); + * } + * + * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop + * transition may cause a state change, but the opposite is not true. If you + * need it, you are probably looking for `componentWillUpdate`. + * + * @param {object} nextProps + * @optional + */ + componentWillReceiveProps: SpecPolicy.DEFINE_MANY, + + /** + * Invoked while deciding if the component should be updated as a result of + * receiving new props, state and/or context. + * + * Use this as an opportunity to `return false` when you're certain that the + * transition to the new props/state/context will not require a component + * update. + * + * shouldComponentUpdate: function(nextProps, nextState, nextContext) { + * return !equal(nextProps, this.props) || + * !equal(nextState, this.state) || + * !equal(nextContext, this.context); + * } + * + * @param {object} nextProps + * @param {?object} nextState + * @param {?object} nextContext + * @return {boolean} True if the component should update. + * @optional + */ + shouldComponentUpdate: SpecPolicy.DEFINE_ONCE, + + /** + * Invoked when the component is about to update due to a transition from + * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState` + * and `nextContext`. + * + * Use this as an opportunity to perform preparation before an update occurs. + * + * NOTE: You **cannot** use `this.setState()` in this method. + * + * @param {object} nextProps + * @param {?object} nextState + * @param {?object} nextContext + * @param {ReactReconcileTransaction} transaction + * @optional + */ + componentWillUpdate: SpecPolicy.DEFINE_MANY, + + /** + * Invoked when the component's DOM representation has been updated. + * + * Use this as an opportunity to operate on the DOM when the component has + * been updated. + * + * @param {object} prevProps + * @param {?object} prevState + * @param {?object} prevContext + * @param {DOMElement} rootNode DOM element representing the component. + * @optional + */ + componentDidUpdate: SpecPolicy.DEFINE_MANY, + + /** + * Invoked when the component is about to be removed from its parent and have + * its DOM representation destroyed. + * + * Use this as an opportunity to deallocate any external resources. + * + * NOTE: There is no `componentDidUnmount` since your component will have been + * destroyed by that point. + * + * @optional + */ + componentWillUnmount: SpecPolicy.DEFINE_MANY, + + + + // ==== Advanced methods ==== + + /** + * Updates the component's currently mounted DOM representation. + * + * By default, this implements React's rendering and reconciliation algorithm. + * Sophisticated clients may wish to override this. + * + * @param {ReactReconcileTransaction} transaction + * @internal + * @overridable + */ + updateComponent: SpecPolicy.OVERRIDE_BASE + +}; + +/** + * Mapping from class specification keys to special processing functions. + * + * Although these are declared like instance properties in the specification + * when defining classes using `React.createClass`, they are actually static + * and are accessible on the constructor instead of the prototype. Despite + * being static, they must be defined outside of the "statics" key under + * which all other static methods are defined. + */ +var RESERVED_SPEC_KEYS = { + displayName: function(Constructor, displayName) { + Constructor.displayName = displayName; + }, + mixins: function(Constructor, mixins) { + if (mixins) { + for (var i = 0; i < mixins.length; i++) { + mixSpecIntoComponent(Constructor, mixins[i]); + } + } + }, + childContextTypes: function(Constructor, childContextTypes) { + if ("production" !== process.env.NODE_ENV) { + validateTypeDef( + Constructor, + childContextTypes, + ReactPropTypeLocations.childContext + ); + } + Constructor.childContextTypes = assign( + {}, + Constructor.childContextTypes, + childContextTypes + ); + }, + contextTypes: function(Constructor, contextTypes) { + if ("production" !== process.env.NODE_ENV) { + validateTypeDef( + Constructor, + contextTypes, + ReactPropTypeLocations.context + ); + } + Constructor.contextTypes = assign( + {}, + Constructor.contextTypes, + contextTypes + ); + }, + /** + * Special case getDefaultProps which should move into statics but requires + * automatic merging. + */ + getDefaultProps: function(Constructor, getDefaultProps) { + if (Constructor.getDefaultProps) { + Constructor.getDefaultProps = createMergedResultFunction( + Constructor.getDefaultProps, + getDefaultProps + ); + } else { + Constructor.getDefaultProps = getDefaultProps; + } + }, + propTypes: function(Constructor, propTypes) { + if ("production" !== process.env.NODE_ENV) { + validateTypeDef( + Constructor, + propTypes, + ReactPropTypeLocations.prop + ); + } + Constructor.propTypes = assign( + {}, + Constructor.propTypes, + propTypes + ); + }, + statics: function(Constructor, statics) { + mixStaticSpecIntoComponent(Constructor, statics); + } +}; + +function validateTypeDef(Constructor, typeDef, location) { + for (var propName in typeDef) { + if (typeDef.hasOwnProperty(propName)) { + // use a warning instead of an invariant so components + // don't show up in prod but not in __DEV__ + ("production" !== process.env.NODE_ENV ? warning( + typeof typeDef[propName] === 'function', + '%s: %s type `%s` is invalid; it must be a function, usually from ' + + 'React.PropTypes.', + Constructor.displayName || 'ReactClass', + ReactPropTypeLocationNames[location], + propName + ) : null); + } + } +} + +function validateMethodOverride(proto, name) { + var specPolicy = ReactClassInterface.hasOwnProperty(name) ? + ReactClassInterface[name] : + null; + + // Disallow overriding of base class methods unless explicitly allowed. + if (ReactClassMixin.hasOwnProperty(name)) { + ("production" !== process.env.NODE_ENV ? invariant( + specPolicy === SpecPolicy.OVERRIDE_BASE, + 'ReactClassInterface: You are attempting to override ' + + '`%s` from your class specification. Ensure that your method names ' + + 'do not overlap with React methods.', + name + ) : invariant(specPolicy === SpecPolicy.OVERRIDE_BASE)); + } + + // Disallow defining methods more than once unless explicitly allowed. + if (proto.hasOwnProperty(name)) { + ("production" !== process.env.NODE_ENV ? invariant( + specPolicy === SpecPolicy.DEFINE_MANY || + specPolicy === SpecPolicy.DEFINE_MANY_MERGED, + 'ReactClassInterface: You are attempting to define ' + + '`%s` on your component more than once. This conflict may be due ' + + 'to a mixin.', + name + ) : invariant(specPolicy === SpecPolicy.DEFINE_MANY || + specPolicy === SpecPolicy.DEFINE_MANY_MERGED)); + } +} + +/** + * Mixin helper which handles policy validation and reserved + * specification keys when building React classses. + */ +function mixSpecIntoComponent(Constructor, spec) { + if (!spec) { + return; + } + + ("production" !== process.env.NODE_ENV ? invariant( + typeof spec !== 'function', + 'ReactClass: You\'re attempting to ' + + 'use a component class as a mixin. Instead, just use a regular object.' + ) : invariant(typeof spec !== 'function')); + ("production" !== process.env.NODE_ENV ? invariant( + !ReactElement.isValidElement(spec), + 'ReactClass: You\'re attempting to ' + + 'use a component as a mixin. Instead, just use a regular object.' + ) : invariant(!ReactElement.isValidElement(spec))); + + var proto = Constructor.prototype; + + // By handling mixins before any other properties, we ensure the same + // chaining order is applied to methods with DEFINE_MANY policy, whether + // mixins are listed before or after these methods in the spec. + if (spec.hasOwnProperty(MIXINS_KEY)) { + RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins); + } + + for (var name in spec) { + if (!spec.hasOwnProperty(name)) { + continue; + } + + if (name === MIXINS_KEY) { + // We have already handled mixins in a special case above + continue; + } + + var property = spec[name]; + validateMethodOverride(proto, name); + + if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { + RESERVED_SPEC_KEYS[name](Constructor, property); + } else { + // Setup methods on prototype: + // The following member methods should not be automatically bound: + // 1. Expected ReactClass methods (in the "interface"). + // 2. Overridden methods (that were mixed in). + var isReactClassMethod = + ReactClassInterface.hasOwnProperty(name); + var isAlreadyDefined = proto.hasOwnProperty(name); + var markedDontBind = property && property.__reactDontBind; + var isFunction = typeof property === 'function'; + var shouldAutoBind = + isFunction && + !isReactClassMethod && + !isAlreadyDefined && + !markedDontBind; + + if (shouldAutoBind) { + if (!proto.__reactAutoBindMap) { + proto.__reactAutoBindMap = {}; + } + proto.__reactAutoBindMap[name] = property; + proto[name] = property; + } else { + if (isAlreadyDefined) { + var specPolicy = ReactClassInterface[name]; + + // These cases should already be caught by validateMethodOverride + ("production" !== process.env.NODE_ENV ? invariant( + isReactClassMethod && ( + (specPolicy === SpecPolicy.DEFINE_MANY_MERGED || specPolicy === SpecPolicy.DEFINE_MANY) + ), + 'ReactClass: Unexpected spec policy %s for key %s ' + + 'when mixing in component specs.', + specPolicy, + name + ) : invariant(isReactClassMethod && ( + (specPolicy === SpecPolicy.DEFINE_MANY_MERGED || specPolicy === SpecPolicy.DEFINE_MANY) + ))); + + // For methods which are defined more than once, call the existing + // methods before calling the new property, merging if appropriate. + if (specPolicy === SpecPolicy.DEFINE_MANY_MERGED) { + proto[name] = createMergedResultFunction(proto[name], property); + } else if (specPolicy === SpecPolicy.DEFINE_MANY) { + proto[name] = createChainedFunction(proto[name], property); + } + } else { + proto[name] = property; + if ("production" !== process.env.NODE_ENV) { + // Add verbose displayName to the function, which helps when looking + // at profiling tools. + if (typeof property === 'function' && spec.displayName) { + proto[name].displayName = spec.displayName + '_' + name; + } + } + } + } + } + } +} + +function mixStaticSpecIntoComponent(Constructor, statics) { + if (!statics) { + return; + } + for (var name in statics) { + var property = statics[name]; + if (!statics.hasOwnProperty(name)) { + continue; + } + + var isReserved = name in RESERVED_SPEC_KEYS; + ("production" !== process.env.NODE_ENV ? invariant( + !isReserved, + 'ReactClass: You are attempting to define a reserved ' + + 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + + 'as an instance property instead; it will still be accessible on the ' + + 'constructor.', + name + ) : invariant(!isReserved)); + + var isInherited = name in Constructor; + ("production" !== process.env.NODE_ENV ? invariant( + !isInherited, + 'ReactClass: You are attempting to define ' + + '`%s` on your component more than once. This conflict may be ' + + 'due to a mixin.', + name + ) : invariant(!isInherited)); + Constructor[name] = property; + } +} + +/** + * Merge two objects, but throw if both contain the same key. + * + * @param {object} one The first object, which is mutated. + * @param {object} two The second object + * @return {object} one after it has been mutated to contain everything in two. + */ +function mergeIntoWithNoDuplicateKeys(one, two) { + ("production" !== process.env.NODE_ENV ? invariant( + one && two && typeof one === 'object' && typeof two === 'object', + 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.' + ) : invariant(one && two && typeof one === 'object' && typeof two === 'object')); + + for (var key in two) { + if (two.hasOwnProperty(key)) { + ("production" !== process.env.NODE_ENV ? invariant( + one[key] === undefined, + 'mergeIntoWithNoDuplicateKeys(): ' + + 'Tried to merge two objects with the same key: `%s`. This conflict ' + + 'may be due to a mixin; in particular, this may be caused by two ' + + 'getInitialState() or getDefaultProps() methods returning objects ' + + 'with clashing keys.', + key + ) : invariant(one[key] === undefined)); + one[key] = two[key]; + } + } + return one; +} + +/** + * Creates a function that invokes two functions and merges their return values. + * + * @param {function} one Function to invoke first. + * @param {function} two Function to invoke second. + * @return {function} Function that invokes the two argument functions. + * @private + */ +function createMergedResultFunction(one, two) { + return function mergedResult() { + var a = one.apply(this, arguments); + var b = two.apply(this, arguments); + if (a == null) { + return b; + } else if (b == null) { + return a; + } + var c = {}; + mergeIntoWithNoDuplicateKeys(c, a); + mergeIntoWithNoDuplicateKeys(c, b); + return c; + }; +} + +/** + * Creates a function that invokes two functions and ignores their return vales. + * + * @param {function} one Function to invoke first. + * @param {function} two Function to invoke second. + * @return {function} Function that invokes the two argument functions. + * @private + */ +function createChainedFunction(one, two) { + return function chainedFunction() { + one.apply(this, arguments); + two.apply(this, arguments); + }; +} + +/** + * Binds a method to the component. + * + * @param {object} component Component whose method is going to be bound. + * @param {function} method Method to be bound. + * @return {function} The bound method. + */ +function bindAutoBindMethod(component, method) { + var boundMethod = method.bind(component); + if ("production" !== process.env.NODE_ENV) { + boundMethod.__reactBoundContext = component; + boundMethod.__reactBoundMethod = method; + boundMethod.__reactBoundArguments = null; + var componentName = component.constructor.displayName; + var _bind = boundMethod.bind; + /* eslint-disable block-scoped-var, no-undef */ + boundMethod.bind = function(newThis ) {for (var args=[],$__0=1,$__1=arguments.length;$__0<$__1;$__0++) args.push(arguments[$__0]); + // User is trying to bind() an autobound method; we effectively will + // ignore the value of "this" that the user is trying to use, so + // let's warn. + if (newThis !== component && newThis !== null) { + ("production" !== process.env.NODE_ENV ? warning( + false, + 'bind(): React component methods may only be bound to the ' + + 'component instance. See %s', + componentName + ) : null); + } else if (!args.length) { + ("production" !== process.env.NODE_ENV ? warning( + false, + 'bind(): You are binding a component method to the component. ' + + 'React does this for you automatically in a high-performance ' + + 'way, so you can safely remove this call. See %s', + componentName + ) : null); + return boundMethod; + } + var reboundMethod = _bind.apply(boundMethod, arguments); + reboundMethod.__reactBoundContext = component; + reboundMethod.__reactBoundMethod = method; + reboundMethod.__reactBoundArguments = args; + return reboundMethod; + /* eslint-enable */ + }; + } + return boundMethod; +} + +/** + * Binds all auto-bound methods in a component. + * + * @param {object} component Component whose method is going to be bound. + */ +function bindAutoBindMethods(component) { + for (var autoBindKey in component.__reactAutoBindMap) { + if (component.__reactAutoBindMap.hasOwnProperty(autoBindKey)) { + var method = component.__reactAutoBindMap[autoBindKey]; + component[autoBindKey] = bindAutoBindMethod( + component, + ReactErrorUtils.guard( + method, + component.constructor.displayName + '.' + autoBindKey + ) + ); + } + } +} + +var typeDeprecationDescriptor = { + enumerable: false, + get: function() { + var displayName = this.displayName || this.name || 'Component'; + ("production" !== process.env.NODE_ENV ? warning( + false, + '%s.type is deprecated. Use %s directly to access the class.', + displayName, + displayName + ) : null); + Object.defineProperty(this, 'type', { + value: this + }); + return this; + } +}; + +/** + * Add more to the ReactClass base class. These are all legacy features and + * therefore not already part of the modern ReactComponent. + */ +var ReactClassMixin = { + + /** + * TODO: This will be deprecated because state should always keep a consistent + * type signature and the only use case for this, is to avoid that. + */ + replaceState: function(newState, callback) { + ReactUpdateQueue.enqueueReplaceState(this, newState); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } + }, + + /** + * Checks whether or not this composite component is mounted. + * @return {boolean} True if mounted, false otherwise. + * @protected + * @final + */ + isMounted: function() { + if ("production" !== process.env.NODE_ENV) { + var owner = ReactCurrentOwner.current; + if (owner !== null) { + ("production" !== process.env.NODE_ENV ? warning( + owner._warnedAboutRefsInRender, + '%s is accessing isMounted inside its render() function. ' + + 'render() should be a pure function of props and state. It should ' + + 'never access something that requires stale data from the previous ' + + 'render, such as refs. Move this logic to componentDidMount and ' + + 'componentDidUpdate instead.', + owner.getName() || 'A component' + ) : null); + owner._warnedAboutRefsInRender = true; + } + } + var internalInstance = ReactInstanceMap.get(this); + return ( + internalInstance && + internalInstance !== ReactLifeCycle.currentlyMountingInstance + ); + }, + + /** + * Sets a subset of the props. + * + * @param {object} partialProps Subset of the next props. + * @param {?function} callback Called after props are updated. + * @final + * @public + * @deprecated + */ + setProps: function(partialProps, callback) { + ReactUpdateQueue.enqueueSetProps(this, partialProps); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } + }, + + /** + * Replace all the props. + * + * @param {object} newProps Subset of the next props. + * @param {?function} callback Called after props are updated. + * @final + * @public + * @deprecated + */ + replaceProps: function(newProps, callback) { + ReactUpdateQueue.enqueueReplaceProps(this, newProps); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } + } +}; + +var ReactClassComponent = function() {}; +assign( + ReactClassComponent.prototype, + ReactComponent.prototype, + ReactClassMixin +); + +/** + * Module for creating composite components. + * + * @class ReactClass + */ +var ReactClass = { + + /** + * Creates a composite component class given a class specification. + * + * @param {object} spec Class specification (which must define `render`). + * @return {function} Component constructor function. + * @public + */ + createClass: function(spec) { + var Constructor = function(props, context) { + // This constructor is overridden by mocks. The argument is used + // by mocks to assert on what gets mounted. + + if ("production" !== process.env.NODE_ENV) { + ("production" !== process.env.NODE_ENV ? warning( + this instanceof Constructor, + 'Something is calling a React component directly. Use a factory or ' + + 'JSX instead. See: http://fb.me/react-legacyfactory' + ) : null); + } + + // Wire up auto-binding + if (this.__reactAutoBindMap) { + bindAutoBindMethods(this); + } + + this.props = props; + this.context = context; + this.state = null; + + // ReactClasses doesn't have constructors. Instead, they use the + // getInitialState and componentWillMount methods for initialization. + + var initialState = this.getInitialState ? this.getInitialState() : null; + if ("production" !== process.env.NODE_ENV) { + // We allow auto-mocks to proceed as if they're returning null. + if (typeof initialState === 'undefined' && + this.getInitialState._isMockFunction) { + // This is probably bad practice. Consider warning here and + // deprecating this convenience. + initialState = null; + } + } + ("production" !== process.env.NODE_ENV ? invariant( + typeof initialState === 'object' && !Array.isArray(initialState), + '%s.getInitialState(): must return an object or null', + Constructor.displayName || 'ReactCompositeComponent' + ) : invariant(typeof initialState === 'object' && !Array.isArray(initialState))); + + this.state = initialState; + }; + Constructor.prototype = new ReactClassComponent(); + Constructor.prototype.constructor = Constructor; + + injectedMixins.forEach( + mixSpecIntoComponent.bind(null, Constructor) + ); + + mixSpecIntoComponent(Constructor, spec); + + // Initialize the defaultProps property after all mixins have been merged + if (Constructor.getDefaultProps) { + Constructor.defaultProps = Constructor.getDefaultProps(); + } + + if ("production" !== process.env.NODE_ENV) { + // This is a tag to indicate that the use of these method names is ok, + // since it's used with createClass. If it's not, then it's likely a + // mistake so we'll warn you to use the static property, property + // initializer or constructor respectively. + if (Constructor.getDefaultProps) { + Constructor.getDefaultProps.isReactClassApproved = {}; + } + if (Constructor.prototype.getInitialState) { + Constructor.prototype.getInitialState.isReactClassApproved = {}; + } + } + + ("production" !== process.env.NODE_ENV ? invariant( + Constructor.prototype.render, + 'createClass(...): Class specification must implement a `render` method.' + ) : invariant(Constructor.prototype.render)); + + if ("production" !== process.env.NODE_ENV) { + ("production" !== process.env.NODE_ENV ? warning( + !Constructor.prototype.componentShouldUpdate, + '%s has a method called ' + + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + + 'The name is phrased as a question because the function is ' + + 'expected to return a value.', + spec.displayName || 'A component' + ) : null); + } + + // Reduce time spent doing lookups by setting these on the prototype. + for (var methodName in ReactClassInterface) { + if (!Constructor.prototype[methodName]) { + Constructor.prototype[methodName] = null; + } + } + + // Legacy hook + Constructor.type = Constructor; + if ("production" !== process.env.NODE_ENV) { + try { + Object.defineProperty(Constructor, 'type', typeDeprecationDescriptor); + } catch (x) { + // IE will fail on defineProperty (es5-shim/sham too) + } + } + + return Constructor; + }, + + injection: { + injectMixin: function(mixin) { + injectedMixins.push(mixin); + } + } + +}; + +module.exports = ReactClass; + +}).call(this,require('_process')) + +},{"./Object.assign":29,"./ReactComponent":39,"./ReactCurrentOwner":45,"./ReactElement":63,"./ReactErrorUtils":66,"./ReactInstanceMap":73,"./ReactLifeCycle":74,"./ReactPropTypeLocationNames":84,"./ReactPropTypeLocations":85,"./ReactUpdateQueue":99,"./invariant":151,"./keyMirror":157,"./keyOf":158,"./warning":172,"_process":174}],39:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponent + */ + +'use strict'; + +var ReactUpdateQueue = require("./ReactUpdateQueue"); + +var invariant = require("./invariant"); +var warning = require("./warning"); + +/** + * Base class helpers for the updating state of a component. + */ +function ReactComponent(props, context) { + this.props = props; + this.context = context; +} + +/** + * Sets a subset of the state. Always use this to mutate + * state. You should treat `this.state` as immutable. + * + * There is no guarantee that `this.state` will be immediately updated, so + * accessing `this.state` after calling this method may return the old value. + * + * There is no guarantee that calls to `setState` will run synchronously, + * as they may eventually be batched together. You can provide an optional + * callback that will be executed when the call to setState is actually + * completed. + * + * When a function is provided to setState, it will be called at some point in + * the future (not synchronously). It will be called with the up to date + * component arguments (state, props, context). These values can be different + * from this.* because your function may be called after receiveProps but before + * shouldComponentUpdate, and this new state, props, and context will not yet be + * assigned to this. + * + * @param {object|function} partialState Next partial state or function to + * produce next partial state to be merged with current state. + * @param {?function} callback Called after state is updated. + * @final + * @protected + */ +ReactComponent.prototype.setState = function(partialState, callback) { + ("production" !== process.env.NODE_ENV ? invariant( + typeof partialState === 'object' || + typeof partialState === 'function' || + partialState == null, + 'setState(...): takes an object of state variables to update or a ' + + 'function which returns an object of state variables.' + ) : invariant(typeof partialState === 'object' || + typeof partialState === 'function' || + partialState == null)); + if ("production" !== process.env.NODE_ENV) { + ("production" !== process.env.NODE_ENV ? warning( + partialState != null, + 'setState(...): You passed an undefined or null state object; ' + + 'instead, use forceUpdate().' + ) : null); + } + ReactUpdateQueue.enqueueSetState(this, partialState); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } +}; + +/** + * Forces an update. This should only be invoked when it is known with + * certainty that we are **not** in a DOM transaction. + * + * You may want to call this when you know that some deeper aspect of the + * component's state has changed but `setState` was not called. + * + * This will not invoke `shouldComponentUpdate`, but it will invoke + * `componentWillUpdate` and `componentDidUpdate`. + * + * @param {?function} callback Called after update is complete. + * @final + * @protected + */ +ReactComponent.prototype.forceUpdate = function(callback) { + ReactUpdateQueue.enqueueForceUpdate(this); + if (callback) { + ReactUpdateQueue.enqueueCallback(this, callback); + } +}; + +/** + * Deprecated APIs. These APIs used to exist on classic React classes but since + * we would like to deprecate them, we're not going to move them over to this + * modern base class. Instead, we define a getter that warns if it's accessed. + */ +if ("production" !== process.env.NODE_ENV) { + var deprecatedAPIs = { + getDOMNode: 'getDOMNode', + isMounted: 'isMounted', + replaceProps: 'replaceProps', + replaceState: 'replaceState', + setProps: 'setProps' + }; + var defineDeprecationWarning = function(methodName, displayName) { + try { + Object.defineProperty(ReactComponent.prototype, methodName, { + get: function() { + ("production" !== process.env.NODE_ENV ? warning( + false, + '%s(...) is deprecated in plain JavaScript React classes.', + displayName + ) : null); + return undefined; + } + }); + } catch (x) { + // IE will fail on defineProperty (es5-shim/sham too) + } + }; + for (var fnName in deprecatedAPIs) { + if (deprecatedAPIs.hasOwnProperty(fnName)) { + defineDeprecationWarning(fnName, deprecatedAPIs[fnName]); + } + } +} + +module.exports = ReactComponent; + +}).call(this,require('_process')) + +},{"./ReactUpdateQueue":99,"./invariant":151,"./warning":172,"_process":174}],40:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponentBrowserEnvironment + */ + +/*jslint evil: true */ + +'use strict'; + +var ReactDOMIDOperations = require("./ReactDOMIDOperations"); +var ReactMount = require("./ReactMount"); + +/** + * Abstracts away all functionality of the reconciler that requires knowledge of + * the browser context. TODO: These callers should be refactored to avoid the + * need for this injection. + */ +var ReactComponentBrowserEnvironment = { + + processChildrenUpdates: + ReactDOMIDOperations.dangerouslyProcessChildrenUpdates, + + replaceNodeWithMarkupByID: + ReactDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, + + /** + * If a particular environment requires that some resources be cleaned up, + * specify this in the injected Mixin. In the DOM, we would likely want to + * purge any cached node ID lookups. + * + * @private + */ + unmountIDFromEnvironment: function(rootNodeID) { + ReactMount.purgeID(rootNodeID); + } + +}; + +module.exports = ReactComponentBrowserEnvironment; + +},{"./ReactDOMIDOperations":50,"./ReactMount":77}],41:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2014-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponentEnvironment + */ + +'use strict'; + +var invariant = require("./invariant"); + +var injected = false; + +var ReactComponentEnvironment = { + + /** + * Optionally injectable environment dependent cleanup hook. (server vs. + * browser etc). Example: A browser system caches DOM nodes based on component + * ID and must remove that cache entry when this instance is unmounted. + */ + unmountIDFromEnvironment: null, + + /** + * Optionally injectable hook for swapping out mount images in the middle of + * the tree. + */ + replaceNodeWithMarkupByID: null, + + /** + * Optionally injectable hook for processing a queue of child updates. Will + * later move into MultiChildComponents. + */ + processChildrenUpdates: null, + + injection: { + injectEnvironment: function(environment) { + ("production" !== process.env.NODE_ENV ? invariant( + !injected, + 'ReactCompositeComponent: injectEnvironment() can only be called once.' + ) : invariant(!injected)); + ReactComponentEnvironment.unmountIDFromEnvironment = + environment.unmountIDFromEnvironment; + ReactComponentEnvironment.replaceNodeWithMarkupByID = + environment.replaceNodeWithMarkupByID; + ReactComponentEnvironment.processChildrenUpdates = + environment.processChildrenUpdates; + injected = true; + } + } + +}; + +module.exports = ReactComponentEnvironment; + +}).call(this,require('_process')) + +},{"./invariant":151,"_process":174}],42:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * +* @providesModule ReactComponentWithPureRenderMixin +*/ + +'use strict'; + +var shallowEqual = require("./shallowEqual"); + +/** + * If your React component's render function is "pure", e.g. it will render the + * same result given the same props and state, provide this Mixin for a + * considerable performance boost. + * + * Most React components have pure render functions. + * + * Example: + * + * var ReactComponentWithPureRenderMixin = + * require('ReactComponentWithPureRenderMixin'); + * React.createClass({ + * mixins: [ReactComponentWithPureRenderMixin], + * + * render: function() { + * return
foo
; + * } + * }); + * + * Note: This only checks shallow equality for props and state. If these contain + * complex data structures this mixin may have false-negatives for deeper + * differences. Only mixin to components which have simple props and state, or + * use `forceUpdate()` when you know deep data structures have changed. + */ +var ReactComponentWithPureRenderMixin = { + shouldComponentUpdate: function(nextProps, nextState) { + return !shallowEqual(this.props, nextProps) || + !shallowEqual(this.state, nextState); + } +}; + +module.exports = ReactComponentWithPureRenderMixin; + +},{"./shallowEqual":167}],43:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactCompositeComponent + */ + +'use strict'; + +var ReactComponentEnvironment = require("./ReactComponentEnvironment"); +var ReactContext = require("./ReactContext"); +var ReactCurrentOwner = require("./ReactCurrentOwner"); +var ReactElement = require("./ReactElement"); +var ReactElementValidator = require("./ReactElementValidator"); +var ReactInstanceMap = require("./ReactInstanceMap"); +var ReactLifeCycle = require("./ReactLifeCycle"); +var ReactNativeComponent = require("./ReactNativeComponent"); +var ReactPerf = require("./ReactPerf"); +var ReactPropTypeLocations = require("./ReactPropTypeLocations"); +var ReactPropTypeLocationNames = require("./ReactPropTypeLocationNames"); +var ReactReconciler = require("./ReactReconciler"); +var ReactUpdates = require("./ReactUpdates"); + +var assign = require("./Object.assign"); +var emptyObject = require("./emptyObject"); +var invariant = require("./invariant"); +var shouldUpdateReactComponent = require("./shouldUpdateReactComponent"); +var warning = require("./warning"); + +function getDeclarationErrorAddendum(component) { + var owner = component._currentElement._owner || null; + if (owner) { + var name = owner.getName(); + if (name) { + return ' Check the render method of `' + name + '`.'; + } + } + return ''; +} + +/** + * ------------------ The Life-Cycle of a Composite Component ------------------ + * + * - constructor: Initialization of state. The instance is now retained. + * - componentWillMount + * - render + * - [children's constructors] + * - [children's componentWillMount and render] + * - [children's componentDidMount] + * - componentDidMount + * + * Update Phases: + * - componentWillReceiveProps (only called if parent updated) + * - shouldComponentUpdate + * - componentWillUpdate + * - render + * - [children's constructors or receive props phases] + * - componentDidUpdate + * + * - componentWillUnmount + * - [children's componentWillUnmount] + * - [children destroyed] + * - (destroyed): The instance is now blank, released by React and ready for GC. + * + * ----------------------------------------------------------------------------- + */ + +/** + * An incrementing ID assigned to each component when it is mounted. This is + * used to enforce the order in which `ReactUpdates` updates dirty components. + * + * @private + */ +var nextMountID = 1; + +/** + * @lends {ReactCompositeComponent.prototype} + */ +var ReactCompositeComponentMixin = { + + /** + * Base constructor for all composite component. + * + * @param {ReactElement} element + * @final + * @internal + */ + construct: function(element) { + this._currentElement = element; + this._rootNodeID = null; + this._instance = null; + + // See ReactUpdateQueue + this._pendingElement = null; + this._pendingStateQueue = null; + this._pendingReplaceState = false; + this._pendingForceUpdate = false; + + this._renderedComponent = null; + + this._context = null; + this._mountOrder = 0; + this._isTopLevel = false; + + // See ReactUpdates and ReactUpdateQueue. + this._pendingCallbacks = null; + }, + + /** + * Initializes the component, renders markup, and registers event listeners. + * + * @param {string} rootID DOM ID of the root node. + * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction + * @return {?string} Rendered markup to be inserted into the DOM. + * @final + * @internal + */ + mountComponent: function(rootID, transaction, context) { + this._context = context; + this._mountOrder = nextMountID++; + this._rootNodeID = rootID; + + var publicProps = this._processProps(this._currentElement.props); + var publicContext = this._processContext(this._currentElement._context); + + var Component = ReactNativeComponent.getComponentClassForElement( + this._currentElement + ); + + // Initialize the public class + var inst = new Component(publicProps, publicContext); + + if ("production" !== process.env.NODE_ENV) { + // This will throw later in _renderValidatedComponent, but add an early + // warning now to help debugging + ("production" !== process.env.NODE_ENV ? warning( + inst.render != null, + '%s(...): No `render` method found on the returned component ' + + 'instance: you may have forgotten to define `render` in your ' + + 'component or you may have accidentally tried to render an element ' + + 'whose type is a function that isn\'t a React component.', + Component.displayName || Component.name || 'Component' + ) : null); + } + + // These should be set up in the constructor, but as a convenience for + // simpler class abstractions, we set them up after the fact. + inst.props = publicProps; + inst.context = publicContext; + inst.refs = emptyObject; + + this._instance = inst; + + // Store a reference from the instance back to the internal representation + ReactInstanceMap.set(inst, this); + + if ("production" !== process.env.NODE_ENV) { + this._warnIfContextsDiffer(this._currentElement._context, context); + } + + if ("production" !== process.env.NODE_ENV) { + // Since plain JS classes are defined without any special initialization + // logic, we can not catch common errors early. Therefore, we have to + // catch them here, at initialization time, instead. + ("production" !== process.env.NODE_ENV ? warning( + !inst.getInitialState || + inst.getInitialState.isReactClassApproved, + 'getInitialState was defined on %s, a plain JavaScript class. ' + + 'This is only supported for classes created using React.createClass. ' + + 'Did you mean to define a state property instead?', + this.getName() || 'a component' + ) : null); + ("production" !== process.env.NODE_ENV ? warning( + !inst.propTypes, + 'propTypes was defined as an instance property on %s. Use a static ' + + 'property to define propTypes instead.', + this.getName() || 'a component' + ) : null); + ("production" !== process.env.NODE_ENV ? warning( + !inst.contextTypes, + 'contextTypes was defined as an instance property on %s. Use a ' + + 'static property to define contextTypes instead.', + this.getName() || 'a component' + ) : null); + ("production" !== process.env.NODE_ENV ? warning( + typeof inst.componentShouldUpdate !== 'function', + '%s has a method called ' + + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + + 'The name is phrased as a question because the function is ' + + 'expected to return a value.', + (this.getName() || 'A component') + ) : null); + } + + var initialState = inst.state; + if (initialState === undefined) { + inst.state = initialState = null; + } + ("production" !== process.env.NODE_ENV ? invariant( + typeof initialState === 'object' && !Array.isArray(initialState), + '%s.state: must be set to an object or null', + this.getName() || 'ReactCompositeComponent' + ) : invariant(typeof initialState === 'object' && !Array.isArray(initialState))); + + this._pendingStateQueue = null; + this._pendingReplaceState = false; + this._pendingForceUpdate = false; + + var renderedElement; + + var previouslyMounting = ReactLifeCycle.currentlyMountingInstance; + ReactLifeCycle.currentlyMountingInstance = this; + try { + if (inst.componentWillMount) { + inst.componentWillMount(); + // When mounting, calls to `setState` by `componentWillMount` will set + // `this._pendingStateQueue` without triggering a re-render. + if (this._pendingStateQueue) { + inst.state = this._processPendingState(inst.props, inst.context); + } + } + + renderedElement = this._renderValidatedComponent(); + } finally { + ReactLifeCycle.currentlyMountingInstance = previouslyMounting; + } + + this._renderedComponent = this._instantiateReactComponent( + renderedElement, + this._currentElement.type // The wrapping type + ); + + var markup = ReactReconciler.mountComponent( + this._renderedComponent, + rootID, + transaction, + this._processChildContext(context) + ); + if (inst.componentDidMount) { + transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); + } + + return markup; + }, + + /** + * Releases any resources allocated by `mountComponent`. + * + * @final + * @internal + */ + unmountComponent: function() { + var inst = this._instance; + + if (inst.componentWillUnmount) { + var previouslyUnmounting = ReactLifeCycle.currentlyUnmountingInstance; + ReactLifeCycle.currentlyUnmountingInstance = this; + try { + inst.componentWillUnmount(); + } finally { + ReactLifeCycle.currentlyUnmountingInstance = previouslyUnmounting; + } + } + + ReactReconciler.unmountComponent(this._renderedComponent); + this._renderedComponent = null; + + // Reset pending fields + this._pendingStateQueue = null; + this._pendingReplaceState = false; + this._pendingForceUpdate = false; + this._pendingCallbacks = null; + this._pendingElement = null; + + // These fields do not really need to be reset since this object is no + // longer accessible. + this._context = null; + this._rootNodeID = null; + + // Delete the reference from the instance to this internal representation + // which allow the internals to be properly cleaned up even if the user + // leaks a reference to the public instance. + ReactInstanceMap.remove(inst); + + // Some existing components rely on inst.props even after they've been + // destroyed (in event handlers). + // TODO: inst.props = null; + // TODO: inst.state = null; + // TODO: inst.context = null; + }, + + /** + * Schedule a partial update to the props. Only used for internal testing. + * + * @param {object} partialProps Subset of the next props. + * @param {?function} callback Called after props are updated. + * @final + * @internal + */ + _setPropsInternal: function(partialProps, callback) { + // This is a deoptimized path. We optimize for always having an element. + // This creates an extra internal element. + var element = this._pendingElement || this._currentElement; + this._pendingElement = ReactElement.cloneAndReplaceProps( + element, + assign({}, element.props, partialProps) + ); + ReactUpdates.enqueueUpdate(this, callback); + }, + + /** + * Filters the context object to only contain keys specified in + * `contextTypes` + * + * @param {object} context + * @return {?object} + * @private + */ + _maskContext: function(context) { + var maskedContext = null; + // This really should be getting the component class for the element, + // but we know that we're not going to need it for built-ins. + if (typeof this._currentElement.type === 'string') { + return emptyObject; + } + var contextTypes = this._currentElement.type.contextTypes; + if (!contextTypes) { + return emptyObject; + } + maskedContext = {}; + for (var contextName in contextTypes) { + maskedContext[contextName] = context[contextName]; + } + return maskedContext; + }, + + /** + * Filters the context object to only contain keys specified in + * `contextTypes`, and asserts that they are valid. + * + * @param {object} context + * @return {?object} + * @private + */ + _processContext: function(context) { + var maskedContext = this._maskContext(context); + if ("production" !== process.env.NODE_ENV) { + var Component = ReactNativeComponent.getComponentClassForElement( + this._currentElement + ); + if (Component.contextTypes) { + this._checkPropTypes( + Component.contextTypes, + maskedContext, + ReactPropTypeLocations.context + ); + } + } + return maskedContext; + }, + + /** + * @param {object} currentContext + * @return {object} + * @private + */ + _processChildContext: function(currentContext) { + var inst = this._instance; + var childContext = inst.getChildContext && inst.getChildContext(); + if (childContext) { + ("production" !== process.env.NODE_ENV ? invariant( + typeof inst.constructor.childContextTypes === 'object', + '%s.getChildContext(): childContextTypes must be defined in order to ' + + 'use getChildContext().', + this.getName() || 'ReactCompositeComponent' + ) : invariant(typeof inst.constructor.childContextTypes === 'object')); + if ("production" !== process.env.NODE_ENV) { + this._checkPropTypes( + inst.constructor.childContextTypes, + childContext, + ReactPropTypeLocations.childContext + ); + } + for (var name in childContext) { + ("production" !== process.env.NODE_ENV ? invariant( + name in inst.constructor.childContextTypes, + '%s.getChildContext(): key "%s" is not defined in childContextTypes.', + this.getName() || 'ReactCompositeComponent', + name + ) : invariant(name in inst.constructor.childContextTypes)); + } + return assign({}, currentContext, childContext); + } + return currentContext; + }, + + /** + * Processes props by setting default values for unspecified props and + * asserting that the props are valid. Does not mutate its argument; returns + * a new props object with defaults merged in. + * + * @param {object} newProps + * @return {object} + * @private + */ + _processProps: function(newProps) { + if ("production" !== process.env.NODE_ENV) { + var Component = ReactNativeComponent.getComponentClassForElement( + this._currentElement + ); + if (Component.propTypes) { + this._checkPropTypes( + Component.propTypes, + newProps, + ReactPropTypeLocations.prop + ); + } + } + return newProps; + }, + + /** + * Assert that the props are valid + * + * @param {object} propTypes Map of prop name to a ReactPropType + * @param {object} props + * @param {string} location e.g. "prop", "context", "child context" + * @private + */ + _checkPropTypes: function(propTypes, props, location) { + // TODO: Stop validating prop types here and only use the element + // validation. + var componentName = this.getName(); + for (var propName in propTypes) { + if (propTypes.hasOwnProperty(propName)) { + var error; + try { + // This is intentionally an invariant that gets caught. It's the same + // behavior as without this statement except with a better message. + ("production" !== process.env.NODE_ENV ? invariant( + typeof propTypes[propName] === 'function', + '%s: %s type `%s` is invalid; it must be a function, usually ' + + 'from React.PropTypes.', + componentName || 'React class', + ReactPropTypeLocationNames[location], + propName + ) : invariant(typeof propTypes[propName] === 'function')); + error = propTypes[propName](props, propName, componentName, location); + } catch (ex) { + error = ex; + } + if (error instanceof Error) { + // We may want to extend this logic for similar errors in + // React.render calls, so I'm abstracting it away into + // a function to minimize refactoring in the future + var addendum = getDeclarationErrorAddendum(this); + + if (location === ReactPropTypeLocations.prop) { + // Preface gives us something to blacklist in warning module + ("production" !== process.env.NODE_ENV ? warning( + false, + 'Failed Composite propType: %s%s', + error.message, + addendum + ) : null); + } else { + ("production" !== process.env.NODE_ENV ? warning( + false, + 'Failed Context Types: %s%s', + error.message, + addendum + ) : null); + } + } + } + } + }, + + receiveComponent: function(nextElement, transaction, nextContext) { + var prevElement = this._currentElement; + var prevContext = this._context; + + this._pendingElement = null; + + this.updateComponent( + transaction, + prevElement, + nextElement, + prevContext, + nextContext + ); + }, + + /** + * If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate` + * is set, update the component. + * + * @param {ReactReconcileTransaction} transaction + * @internal + */ + performUpdateIfNecessary: function(transaction) { + if (this._pendingElement != null) { + ReactReconciler.receiveComponent( + this, + this._pendingElement || this._currentElement, + transaction, + this._context + ); + } + + if (this._pendingStateQueue !== null || this._pendingForceUpdate) { + if ("production" !== process.env.NODE_ENV) { + ReactElementValidator.checkAndWarnForMutatedProps( + this._currentElement + ); + } + + this.updateComponent( + transaction, + this._currentElement, + this._currentElement, + this._context, + this._context + ); + } + }, + + /** + * Compare two contexts, warning if they are different + * TODO: Remove this check when owner-context is removed + */ + _warnIfContextsDiffer: function(ownerBasedContext, parentBasedContext) { + ownerBasedContext = this._maskContext(ownerBasedContext); + parentBasedContext = this._maskContext(parentBasedContext); + var parentKeys = Object.keys(parentBasedContext).sort(); + var displayName = this.getName() || 'ReactCompositeComponent'; + for (var i = 0; i < parentKeys.length; i++) { + var key = parentKeys[i]; + ("production" !== process.env.NODE_ENV ? warning( + ownerBasedContext[key] === parentBasedContext[key], + 'owner-based and parent-based contexts differ ' + + '(values: `%s` vs `%s`) for key (%s) while mounting %s ' + + '(see: http://fb.me/react-context-by-parent)', + ownerBasedContext[key], + parentBasedContext[key], + key, + displayName + ) : null); + } + }, + + /** + * Perform an update to a mounted component. The componentWillReceiveProps and + * shouldComponentUpdate methods are called, then (assuming the update isn't + * skipped) the remaining update lifecycle methods are called and the DOM + * representation is updated. + * + * By default, this implements React's rendering and reconciliation algorithm. + * Sophisticated clients may wish to override this. + * + * @param {ReactReconcileTransaction} transaction + * @param {ReactElement} prevParentElement + * @param {ReactElement} nextParentElement + * @internal + * @overridable + */ + updateComponent: function( + transaction, + prevParentElement, + nextParentElement, + prevUnmaskedContext, + nextUnmaskedContext + ) { + var inst = this._instance; + + var nextContext = inst.context; + var nextProps = inst.props; + + // Distinguish between a props update versus a simple state update + if (prevParentElement !== nextParentElement) { + nextContext = this._processContext(nextParentElement._context); + nextProps = this._processProps(nextParentElement.props); + + if ("production" !== process.env.NODE_ENV) { + if (nextUnmaskedContext != null) { + this._warnIfContextsDiffer( + nextParentElement._context, + nextUnmaskedContext + ); + } + } + + // An update here will schedule an update but immediately set + // _pendingStateQueue which will ensure that any state updates gets + // immediately reconciled instead of waiting for the next batch. + + if (inst.componentWillReceiveProps) { + inst.componentWillReceiveProps(nextProps, nextContext); + } + } + + var nextState = this._processPendingState(nextProps, nextContext); + + var shouldUpdate = + this._pendingForceUpdate || + !inst.shouldComponentUpdate || + inst.shouldComponentUpdate(nextProps, nextState, nextContext); + + if ("production" !== process.env.NODE_ENV) { + ("production" !== process.env.NODE_ENV ? warning( + typeof shouldUpdate !== 'undefined', + '%s.shouldComponentUpdate(): Returned undefined instead of a ' + + 'boolean value. Make sure to return true or false.', + this.getName() || 'ReactCompositeComponent' + ) : null); + } + + if (shouldUpdate) { + this._pendingForceUpdate = false; + // Will set `this.props`, `this.state` and `this.context`. + this._performComponentUpdate( + nextParentElement, + nextProps, + nextState, + nextContext, + transaction, + nextUnmaskedContext + ); + } else { + // If it's determined that a component should not update, we still want + // to set props and state but we shortcut the rest of the update. + this._currentElement = nextParentElement; + this._context = nextUnmaskedContext; + inst.props = nextProps; + inst.state = nextState; + inst.context = nextContext; + } + }, + + _processPendingState: function(props, context) { + var inst = this._instance; + var queue = this._pendingStateQueue; + var replace = this._pendingReplaceState; + this._pendingReplaceState = false; + this._pendingStateQueue = null; + + if (!queue) { + return inst.state; + } + + var nextState = assign({}, replace ? queue[0] : inst.state); + for (var i = replace ? 1 : 0; i < queue.length; i++) { + var partial = queue[i]; + assign( + nextState, + typeof partial === 'function' ? + partial.call(inst, nextState, props, context) : + partial + ); + } + + return nextState; + }, + + /** + * Merges new props and state, notifies delegate methods of update and + * performs update. + * + * @param {ReactElement} nextElement Next element + * @param {object} nextProps Next public object to set as properties. + * @param {?object} nextState Next object to set as state. + * @param {?object} nextContext Next public object to set as context. + * @param {ReactReconcileTransaction} transaction + * @param {?object} unmaskedContext + * @private + */ + _performComponentUpdate: function( + nextElement, + nextProps, + nextState, + nextContext, + transaction, + unmaskedContext + ) { + var inst = this._instance; + + var prevProps = inst.props; + var prevState = inst.state; + var prevContext = inst.context; + + if (inst.componentWillUpdate) { + inst.componentWillUpdate(nextProps, nextState, nextContext); + } + + this._currentElement = nextElement; + this._context = unmaskedContext; + inst.props = nextProps; + inst.state = nextState; + inst.context = nextContext; + + this._updateRenderedComponent(transaction, unmaskedContext); + + if (inst.componentDidUpdate) { + transaction.getReactMountReady().enqueue( + inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), + inst + ); + } + }, + + /** + * Call the component's `render` method and update the DOM accordingly. + * + * @param {ReactReconcileTransaction} transaction + * @internal + */ + _updateRenderedComponent: function(transaction, context) { + var prevComponentInstance = this._renderedComponent; + var prevRenderedElement = prevComponentInstance._currentElement; + var nextRenderedElement = this._renderValidatedComponent(); + if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { + ReactReconciler.receiveComponent( + prevComponentInstance, + nextRenderedElement, + transaction, + this._processChildContext(context) + ); + } else { + // These two IDs are actually the same! But nothing should rely on that. + var thisID = this._rootNodeID; + var prevComponentID = prevComponentInstance._rootNodeID; + ReactReconciler.unmountComponent(prevComponentInstance); + + this._renderedComponent = this._instantiateReactComponent( + nextRenderedElement, + this._currentElement.type + ); + var nextMarkup = ReactReconciler.mountComponent( + this._renderedComponent, + thisID, + transaction, + context + ); + this._replaceNodeWithMarkupByID(prevComponentID, nextMarkup); + } + }, + + /** + * @protected + */ + _replaceNodeWithMarkupByID: function(prevComponentID, nextMarkup) { + ReactComponentEnvironment.replaceNodeWithMarkupByID( + prevComponentID, + nextMarkup + ); + }, + + /** + * @protected + */ + _renderValidatedComponentWithoutOwnerOrContext: function() { + var inst = this._instance; + var renderedComponent = inst.render(); + if ("production" !== process.env.NODE_ENV) { + // We allow auto-mocks to proceed as if they're returning null. + if (typeof renderedComponent === 'undefined' && + inst.render._isMockFunction) { + // This is probably bad practice. Consider warning here and + // deprecating this convenience. + renderedComponent = null; + } + } + + return renderedComponent; + }, + + /** + * @private + */ + _renderValidatedComponent: function() { + var renderedComponent; + var previousContext = ReactContext.current; + ReactContext.current = this._processChildContext( + this._currentElement._context + ); + ReactCurrentOwner.current = this; + try { + renderedComponent = + this._renderValidatedComponentWithoutOwnerOrContext(); + } finally { + ReactContext.current = previousContext; + ReactCurrentOwner.current = null; + } + ("production" !== process.env.NODE_ENV ? invariant( + // TODO: An `isValidNode` function would probably be more appropriate + renderedComponent === null || renderedComponent === false || + ReactElement.isValidElement(renderedComponent), + '%s.render(): A valid ReactComponent must be returned. You may have ' + + 'returned undefined, an array or some other invalid object.', + this.getName() || 'ReactCompositeComponent' + ) : invariant(// TODO: An `isValidNode` function would probably be more appropriate + renderedComponent === null || renderedComponent === false || + ReactElement.isValidElement(renderedComponent))); + return renderedComponent; + }, + + /** + * Lazily allocates the refs object and stores `component` as `ref`. + * + * @param {string} ref Reference name. + * @param {component} component Component to store as `ref`. + * @final + * @private + */ + attachRef: function(ref, component) { + var inst = this.getPublicInstance(); + var refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs; + refs[ref] = component.getPublicInstance(); + }, + + /** + * Detaches a reference name. + * + * @param {string} ref Name to dereference. + * @final + * @private + */ + detachRef: function(ref) { + var refs = this.getPublicInstance().refs; + delete refs[ref]; + }, + + /** + * Get a text description of the component that can be used to identify it + * in error messages. + * @return {string} The name or null. + * @internal + */ + getName: function() { + var type = this._currentElement.type; + var constructor = this._instance && this._instance.constructor; + return ( + type.displayName || (constructor && constructor.displayName) || + type.name || (constructor && constructor.name) || + null + ); + }, + + /** + * Get the publicly accessible representation of this component - i.e. what + * is exposed by refs and returned by React.render. Can be null for stateless + * components. + * + * @return {ReactComponent} the public component instance. + * @internal + */ + getPublicInstance: function() { + return this._instance; + }, + + // Stub + _instantiateReactComponent: null + +}; + +ReactPerf.measureMethods( + ReactCompositeComponentMixin, + 'ReactCompositeComponent', + { + mountComponent: 'mountComponent', + updateComponent: 'updateComponent', + _renderValidatedComponent: '_renderValidatedComponent' + } +); + +var ReactCompositeComponent = { + + Mixin: ReactCompositeComponentMixin + +}; + +module.exports = ReactCompositeComponent; + +}).call(this,require('_process')) + +},{"./Object.assign":29,"./ReactComponentEnvironment":41,"./ReactContext":44,"./ReactCurrentOwner":45,"./ReactElement":63,"./ReactElementValidator":64,"./ReactInstanceMap":73,"./ReactLifeCycle":74,"./ReactNativeComponent":80,"./ReactPerf":82,"./ReactPropTypeLocationNames":84,"./ReactPropTypeLocations":85,"./ReactReconciler":89,"./ReactUpdates":100,"./emptyObject":131,"./invariant":151,"./shouldUpdateReactComponent":168,"./warning":172,"_process":174}],44:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactContext + */ + +'use strict'; + +var assign = require("./Object.assign"); +var emptyObject = require("./emptyObject"); +var warning = require("./warning"); + +var didWarn = false; + +/** + * Keeps track of the current context. + * + * The context is automatically passed down the component ownership hierarchy + * and is accessible via `this.context` on ReactCompositeComponents. + */ +var ReactContext = { + + /** + * @internal + * @type {object} + */ + current: emptyObject, + + /** + * Temporarily extends the current context while executing scopedCallback. + * + * A typical use case might look like + * + * render: function() { + * var children = ReactContext.withContext({foo: 'foo'}, () => ( + * + * )); + * return
{children}
; + * } + * + * @param {object} newContext New context to merge into the existing context + * @param {function} scopedCallback Callback to run with the new context + * @return {ReactComponent|array} + */ + withContext: function(newContext, scopedCallback) { + if ("production" !== process.env.NODE_ENV) { + ("production" !== process.env.NODE_ENV ? warning( + didWarn, + 'withContext is deprecated and will be removed in a future version. ' + + 'Use a wrapper component with getChildContext instead.' + ) : null); + + didWarn = true; + } + + var result; + var previousContext = ReactContext.current; + ReactContext.current = assign({}, previousContext, newContext); + try { + result = scopedCallback(); + } finally { + ReactContext.current = previousContext; + } + return result; + } + +}; + +module.exports = ReactContext; + +}).call(this,require('_process')) + +},{"./Object.assign":29,"./emptyObject":131,"./warning":172,"_process":174}],45:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactCurrentOwner + */ + +'use strict'; + +/** + * Keeps track of the current owner. + * + * The current owner is the component who should own any components that are + * currently being constructed. + * + * The depth indicate how many composite components are above this render level. + */ +var ReactCurrentOwner = { + + /** + * @internal + * @type {ReactComponent} + */ + current: null + +}; + +module.exports = ReactCurrentOwner; + +},{}],46:[function(require,module,exports){ +(function (process){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDOM + * @typechecks static-only + */ + +'use strict'; + +var ReactElement = require("./ReactElement"); +var ReactElementValidator = require("./ReactElementValidator"); + +var mapObject = require("./mapObject"); + +/** + * Create a factory that creates HTML tag elements. + * + * @param {string} tag Tag name (e.g. `div`). + * @private + */ +function createDOMFactory(tag) { + if ("production" !== process.env.NODE_ENV) { + return ReactElementValidator.createFactory(tag); + } + return ReactElement.createFactory(tag); +} + +/** + * Creates a mapping from supported HTML tags to `ReactDOMComponent` classes. + * This is also accessible via `React.DOM`. + * + * @public + */ +var ReactDOM = mapObject({ + a: 'a', + abbr: 'abbr', + address: 'address', + area: 'area', + article: 'article', + aside: 'aside', + audio: 'audio', + b: 'b', + base: 'base', + bdi: 'bdi', + bdo: 'bdo', + big: 'big', + blockquote: 'blockquote', + body: 'body', + br: 'br', + button: 'button', + canvas: 'canvas', + caption: 'caption', + cite: 'cite', + code: 'code', + col: 'col', + colgroup: 'colgroup', + data: 'data', + datalist: 'datalist', + dd: 'dd', + del: 'del', + details: 'details', + dfn: 'dfn', + dialog: 'dialog', + div: 'div', + dl: 'dl', + dt: 'dt', + em: 'em', + embed: 'embed', + fieldset: 'fieldset', + figcaption: 'figcaption', + figure: 'figure', + footer: 'footer', + form: 'form', + h1: 'h1', + h2: 'h2', + h3: 'h3', + h4: 'h4', + h5: 'h5', + h6: 'h6', + head: 'head', + header: 'header', + hr: 'hr', + html: 'html', + i: 'i', + iframe: 'iframe', + img: 'img', + input: 'input', + ins: 'ins', + kbd: 'kbd', + keygen: 'keygen', + label: 'label', + legend: 'legend', + li: 'li', + link: 'link', + main: 'main', + map: 'map', + mark: 'mark', + menu: 'menu', + menuitem: 'menuitem', + meta: 'meta', + meter: 'meter', + nav: 'nav', + noscript: 'noscript', + object: 'object', + ol: 'ol', + optgroup: 'optgroup', + option: 'option', + output: 'output', + p: 'p', + param: 'param', + picture: 'picture', + pre: 'pre', + progress: 'progress', + q: 'q', + rp: 'rp', + rt: 'rt', + ruby: 'ruby', + s: 's', + samp: 'samp', + script: 'script', + section: 'section', + select: 'select', + small: 'small', + source: 'source', + span: 'span', + strong: 'strong', + style: 'style', + sub: 'sub', + summary: 'summary', + sup: 'sup', + table: 'table', + tbody: 'tbody', + td: 'td', + textarea: 'textarea', + tfoot: 'tfoot', + th: 'th', + thead: 'thead', + time: 'time', + title: 'title', + tr: 'tr', + track: 'track', + u: 'u', + ul: 'ul', + 'var': 'var', + video: 'video', + wbr: 'wbr', + + // SVG + circle: 'circle', + defs: 'defs', + ellipse: 'ellipse', + g: 'g', + line: 'line', + linearGradient: 'linearGradient', + mask: 'mask', + path: 'path', + pattern: 'pattern', + polygon: 'polygon', + polyline: 'polyline', + radialGradient: 'radialGradient', + rect: 'rect', + stop: 'stop', + svg: 'svg', + text: 'text', + tspan: 'tspan' + +}, createDOMFactory); + +module.exports = ReactDOM; + +}).call(this,require('_process')) + +},{"./ReactElement":63,"./ReactElementValidator":64,"./mapObject":159,"_process":174}],47:[function(require,module,exports){ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDOMButton + */ + +'use strict'; + +var AutoFocusMixin = require("./AutoFocusMixin"); +var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin"); +var ReactClass = require("./ReactClass"); +var ReactElement = require("./ReactElement"); + +var keyMirror = require("./keyMirror"); + +var button = ReactElement.createFactory('button'); + +var mouseListenerNames = keyMirror({ + onClick: true, + onDoubleClick: true, + onMouseDown: true, + onMouseMove: true, + onMouseUp: true, + onClickCapture: true, + onDoubleClickCapture: true, + onMouseDownCapture: true, + onMouseMoveCapture: true, + onMouseUpCapture: true +}); + +/** + * Implements a