diff --git a/perf/lib/BrowserPerfRunnerApp.react.js b/perf/lib/BrowserPerfRunnerApp.react.js index 5b34f3bcaaec5..8adca1c9bf1a1 100644 --- a/perf/lib/BrowserPerfRunnerApp.react.js +++ b/perf/lib/BrowserPerfRunnerApp.react.js @@ -33,7 +33,7 @@ var BrowserPerfRunnerApp = React.createClass({ handleComplete: function(queueItem){ queueItem.completed = true; - + if (!this.props.onCompleteEach) { return; } @@ -53,7 +53,7 @@ var BrowserPerfRunnerApp = React.createClass({ .map(function(key){return this.state.results[key];}, this) ; this.props.onCompleteEach(resultsForAllVersions); - + if (this.props.onComplete && incompleteCount === 0) { this.props.onComplete(this.state.results); } @@ -99,7 +99,7 @@ BrowserPerfRunnerApp.renderBenchmarkCell = function(props, row, col){ return benchmark && !benchmark.isRunning && benchmark.stats; }) ; - + if (col == null) return React.DOM.th({style:{verticalAlign:'top', textAlign:'right'}}, React.DOM.a({href:'?test=' + row}, benchmarks[0] && benchmarks[0].name || row) ); @@ -107,8 +107,8 @@ BrowserPerfRunnerApp.renderBenchmarkCell = function(props, row, col){ var key = row + '@' + col; var benchmark = props.value[key]; if (!(benchmark && benchmark.stats)) return React.DOM.td({key:key}); - - + + var colors = [ '000000', 'AA0000', @@ -197,8 +197,11 @@ var GridViewTable = React.createClass({ render: function(){ return React.DOM.table(null, - this._renderRow(null, 0), - this.props.rows.map(this._renderRow, this) + React.DOM.tbody( + null, + this._renderRow(null, 0), + this.props.rows.map(this._renderRow, this) + ) ); } diff --git a/perf/lib/perf-test-runner.browser.js b/perf/lib/perf-test-runner.browser.js index 26e059f8c38c7..e02191a625860 100644 --- a/perf/lib/perf-test-runner.browser.js +++ b/perf/lib/perf-test-runner.browser.js @@ -40,9 +40,9 @@ perfRunner.WriteReactLibScript = function(params){ } else { minSuffix = '.min'; } - + if (params.version && typeof params.version != 'string') throw TypeError("Expected 'version' to be a string"); - + if (params.version == 'edge' || !params.version) { console.log('React edge (local)'); perfRunner.WriteScript({src:'../build/react' + minSuffix + '.js'}); @@ -83,7 +83,7 @@ perfRunner.getQueryParamArray = function(key){ var values; var queryString = location.search.substr(1); var _key = encodeURIComponent(key) + '='; - + if (queryString.indexOf(_key) > -1) { values = queryString .split(_key) @@ -98,7 +98,7 @@ perfRunner.getQueryParamArray = function(key){ }) ; } - + perfRunner.assert(values && values.length && values[0], 'expected ' + key + ' query param'); return values; } @@ -142,7 +142,7 @@ perfRunner.quickBench = function(benchmarkOptions, onComplete, onBeforeStart){ bench.on('cycle', function(){ var bench = this, size = bench.stats.size; - + if (!bench.aborted) { console.warn(bench.name + ' × ' + bench.count + ' (' + bench.stats.sample.length + ' samples)' + @@ -162,12 +162,12 @@ perfRunner.quickBench = function(benchmarkOptions, onComplete, onBeforeStart){ // times: bench.times, // stats: bench.stats }; - + results['s/op'] = bench.stats.mean results['ms/op'] = results['s/op'] * 1000 results['op/s'] = 1 / results['s/op'] results["% frame 60"] = results['ms/op'] / (1000 / 60) * 100 - + console.log(results); onComplete(null, results); }); @@ -192,13 +192,15 @@ perfRunner.singleTest = function(benchmarkOptions, onComplete){ perfRunner.ViewObject = function(props){ var value = props.value; delete props.value; - + if (typeof value != 'object') return React.DOM.span(props, [JSON.stringify(value), " ", typeof value]); - - return React.DOM.table(props, Object.keys(value).map(function(key){ - return React.DOM.tr(null, - React.DOM.th(null, key), - React.DOM.td(null, perfRunner.ViewObject({key:key, value:value[key]})) - ); - })); + + return React.DOM.table(props, + React.DOM.tbody(null, Object.keys(value).map(function(key){ + return React.DOM.tr(null, + React.DOM.th(null, key), + React.DOM.td(null, perfRunner.ViewObject({key:key, value:value[key]})) + ) + })) + ); } diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js index e898b5899488f..cd4ac34d9c42f 100644 --- a/src/browser/ui/ReactMount.js +++ b/src/browser/ui/ReactMount.js @@ -94,11 +94,53 @@ function getID(node) { return id; } +function idToNodeID(id) { + return id.substr(id.lastIndexOf('.') + 1); +} + +function getNodeID(node, ancestorID) { + var id = node && node.getAttribute && node.getAttribute(ATTR_NAME); + id = id != null ? ancestorID + '.' + id : ''; + + if (id) { + if (nodeCache.hasOwnProperty(id)) { + var cached = nodeCache[id]; + if (cached !== node) { + invariant( + !isValid(cached, id), + 'ReactMount: Two valid but unequal nodes with the same `%s`: %s', + ATTR_NAME, id + ); + + nodeCache[id] = node; + } + } else { + nodeCache[id] = node; + } + } + + return id; +} + function internalGetID(node) { // If node is something like a window, document, or text node, none of // which support attributes or a .getAttribute method, gracefully return // the empty string, as if the attribute were missing. - return node && node.getAttribute && node.getAttribute(ATTR_NAME) || ''; + var id = ''; + while (node && node.getAttribute) { + var value = node.getAttribute(ATTR_NAME); + if (value == null) { + break; + } + id = '.' + value + id; + if (value[0] === '#') { + break; + } + node = node.parentNode; + } + return id; + + //return node && node.getAttribute && node.getAttribute(ATTR_NAME) || ''; } /** @@ -112,7 +154,7 @@ function setID(node, id) { if (oldID !== id) { delete nodeCache[oldID]; } - node.setAttribute(ATTR_NAME, id); + node.setAttribute(ATTR_NAME, idToNodeID(id)); nodeCache[id] = node; } @@ -142,11 +184,13 @@ function getNode(id) { */ function isValid(node, id) { if (node) { - invariant( - internalGetID(node) === id, - 'ReactMount: Unexpected modification of `%s`', - ATTR_NAME - ); + if (__DEV__) { + invariant( + internalGetID(node) === id, + 'ReactMount: Unexpected modification of `%s`', + ATTR_NAME + ); + } var container = ReactMount.findReactContainerForID(id); if (container && containsNode(container, node)) { @@ -166,11 +210,11 @@ function purgeID(id) { delete nodeCache[id]; } -var deepestNodeSoFar = null; -function findDeepestCachedAncestorImpl(ancestorID) { +var deepestNodeIDSoFar = null; +function findDeepestCachedAncestorIDImpl(ancestorID) { var ancestor = nodeCache[ancestorID]; if (ancestor && isValid(ancestor, ancestorID)) { - deepestNodeSoFar = ancestor; + deepestNodeIDSoFar = ancestorID; } else { // This node isn't populated in the cache, so presumably none of its // descendants are. Break out of the loop. @@ -181,16 +225,16 @@ function findDeepestCachedAncestorImpl(ancestorID) { /** * Return the deepest cached node whose ID is a prefix of `targetID`. */ -function findDeepestCachedAncestor(targetID) { - deepestNodeSoFar = null; +function findDeepestCachedAncestorID(targetID) { + deepestNodeIDSoFar = null; ReactInstanceHandles.traverseAncestors( targetID, - findDeepestCachedAncestorImpl + findDeepestCachedAncestorIDImpl ); - var foundNode = deepestNodeSoFar; - deepestNodeSoFar = null; - return foundNode; + var foundNodeID = deepestNodeIDSoFar; + deepestNodeIDSoFar = null; + return foundNodeID; } /** @@ -549,8 +593,7 @@ var ReactMount = { // Not a DOMElement, therefore not a React component return false; } - var id = ReactMount.getID(node); - return id ? id.charAt(0) === SEPARATOR : false; + return node.hasAttribute && node.hasAttribute(ATTR_NAME); }, /** @@ -586,25 +629,42 @@ var ReactMount = { var firstChildren = findComponentRootReusableArray; var childIndex = 0; - var deepestAncestor = findDeepestCachedAncestor(targetID) || ancestorNode; + var deepestAncestorID = findDeepestCachedAncestorID(targetID); + var deepestAncestor; + if (deepestAncestorID) { + deepestAncestor = ReactMount.getNode(deepestAncestorID); + } else { + deepestAncestorID = ReactMount.getID(ancestorNode); + deepestAncestor = ancestorNode; + } firstChildren[0] = deepestAncestor.firstChild; firstChildren.length = 1; + var ancestorID = deepestAncestorID; + while (childIndex < firstChildren.length) { var child = firstChildren[childIndex++]; - var targetChild; while (child) { - var childID = ReactMount.getID(child); + var childID = ReactMount.getNodeID(child, ancestorID); + if (childID) { // Even if we find the node we're looking for, we finish looping // through its siblings to ensure they're cached so that we don't have // to revisit this node again. Otherwise, we make n^2 calls to getID // when visiting the many children of a single node in order. + //console.log(targetID, childID); if (targetID === childID) { - targetChild = child; + var targetChild = child; + + // Emptying firstChildren/findComponentRootReusableArray is + // not necessary for correctness, but it helps the GC reclaim + // any nodes that were left at the end of the search. + firstChildren.length = 0; + + return targetChild; } else if (ReactInstanceHandles.isAncestorIDOf(childID, targetID)) { // If we find a child whose ID is an ancestor of the given ID, // then we can be sure that we only want to search the subtree @@ -612,6 +672,8 @@ var ReactMount = { // search state. firstChildren.length = childIndex = 0; firstChildren.push(child.firstChild); + ancestorID = childID; + break; } } else { @@ -625,19 +687,12 @@ var ReactMount = { child = child.nextSibling; } - - if (targetChild) { - // Emptying firstChildren/findComponentRootReusableArray is - // not necessary for correctness, but it helps the GC reclaim - // any nodes that were left at the end of the search. - firstChildren.length = 0; - - return targetChild; - } } firstChildren.length = 0; + //console.log(ancestorID); + invariant( false, 'findComponentRoot(..., %s): Unable to find element. This probably ' + @@ -659,6 +714,8 @@ var ReactMount = { getID: getID, + getNodeID: getNodeID, + setID: setID, getNode: getNode, diff --git a/src/browser/ui/dom/DOMPropertyOperations.js b/src/browser/ui/dom/DOMPropertyOperations.js index 12a3502bb9f1f..6cea1b0b8c749 100644 --- a/src/browser/ui/dom/DOMPropertyOperations.js +++ b/src/browser/ui/dom/DOMPropertyOperations.js @@ -81,7 +81,7 @@ var DOMPropertyOperations = { */ createMarkupForID: function(id) { return processAttributeNameAndPrefix(DOMProperty.ID_ATTRIBUTE_NAME) + - escapeTextForBrowser(id) + '"'; + escapeTextForBrowser(id.substr(id.lastIndexOf('.') + 1)) + '"'; }, /** diff --git a/src/core/ReactInstanceHandles.js b/src/core/ReactInstanceHandles.js index b1a07cf34ff1f..17c0451975dca 100644 --- a/src/core/ReactInstanceHandles.js +++ b/src/core/ReactInstanceHandles.js @@ -229,7 +229,7 @@ var ReactInstanceHandles = { * @return {string} A React root ID. */ createReactRootID: function() { - return getReactRootIDString(ReactRootIndex.createReactRootIndex()); + return getReactRootIDString('#' + ReactRootIndex.createReactRootIndex()); }, /**