Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 6b4be3a

Browse files
authored
Detect DEV version of React (#667)
* Detect DEV version of React * Outdated popup * Check for outdated versions * Filter out bad toString results * Detect DEV for Fiber (works with master) * Tweak the wording so the link looks nicer
1 parent 695c909 commit 6b4be3a

File tree

8 files changed

+138
-15
lines changed

8 files changed

+138
-15
lines changed

backend/installGlobalHook.js

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,94 @@ function installGlobalHook(window: Object) {
2020
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
2121
return;
2222
}
23+
function detectReactBuildType(renderer) {
24+
try {
25+
var toString = Function.prototype.toString;
26+
if (typeof renderer.version === 'string') {
27+
// React DOM Fiber (16+)
28+
if (renderer.bundleType > 0) {
29+
// This is not a production build.
30+
// We are currently only using 0 (PROD) and 1 (DEV)
31+
// but might add 2 (PROFILE) in the future.
32+
return 'development';
33+
}
34+
// The above should cover envification, but we should still make sure
35+
// that the bundle code has been uglified.
36+
var findFiberCode = toString.call(renderer.findFiberByHostInstance);
37+
// Filter out bad results (if that is even possible):
38+
if (findFiberCode.indexOf('function') !== 0) {
39+
// Hope for the best if we're not sure.
40+
return 'production';
41+
}
42+
// By now we know that it's envified--but what if it's not minified?
43+
// This can be bad too, as it means DEV code is still there.
44+
// Let's check the first argument. It should be a single letter.
45+
if (!(/function[\s\w]*\(\w[,\)]/.test(findFiberCode))) {
46+
return 'development';
47+
}
48+
// We're good.
49+
return 'production';
50+
}
51+
if (renderer.Mount && renderer.Mount._renderNewRootComponent) {
52+
// React DOM Stack
53+
var renderRootCode = toString.call(renderer.Mount._renderNewRootComponent);
54+
// Filter out bad results (if that is even possible):
55+
if (renderRootCode.indexOf('function') !== 0) {
56+
// Hope for the best if we're not sure.
57+
return 'production';
58+
}
59+
// React DOM Stack < 15.1.0
60+
// If it contains "storedMeasure" call, it's wrapped in ReactPerf (DEV only).
61+
// This would be true even if it's minified, as method name still matches.
62+
if (renderRootCode.indexOf('storedMeasure') !== -1) {
63+
return 'development';
64+
}
65+
// React DOM Stack >= 15.1.0, < 16
66+
// If it contains a warning message, it's a DEV build.
67+
// This would be true even if it's minified, as the message would stay.
68+
if (renderRootCode.indexOf('should be a pure function') !== -1) {
69+
return 'development';
70+
}
71+
// By now we know that it's envified--but what if it's not minified?
72+
// This can be bad too, as it means DEV code is still there.
73+
// Let's check the first argument. It should be a single letter.
74+
// We know this function gets more than one argument in all supported
75+
// versions, and if it doesn't have arguments, it's wrapped in ReactPerf
76+
// (which also indicates a DEV build, although we should've filtered
77+
// that out earlier).
78+
if (!(/function\s*\(\w\,/.test(renderRootCode))) {
79+
return 'development';
80+
}
81+
// Seems like we're using the production version.
82+
// Now let's check if we're still on 0.14 or lower:
83+
if (renderRootCode.indexOf('._registerComponent') !== -1) {
84+
// TODO: we can remove the condition above once 16
85+
// is older than a year. Since this branch only runs
86+
// for Stack, we can flip it completely when Stack
87+
// is old enough. The branch for Fiber is above,
88+
// and it can check renderer.version directly.
89+
return 'outdated';
90+
}
91+
// We're all good.
92+
return 'production';
93+
}
94+
} catch (err) {
95+
// Weird environments may exist.
96+
// This code needs a higher fault tolerance
97+
// because it runs even with closed DevTools.
98+
// TODO: should we catch errors in all injected code, and not just this part?
99+
}
100+
return 'production';
101+
}
23102
const hook = ({
24103
// Shared between Stack and Fiber:
25104
_renderers: {},
26105
helpers: {},
27106
inject: function(renderer) {
28-
if (typeof renderer.version === 'number' && renderer.version > 1) {
29-
// This Fiber version is too new and not supported yet.
30-
// The version field is declared in ReactFiberDevToolsHook.
31-
// Only Fiber releases have the version field.
32-
return null;
33-
}
34107
var id = Math.random().toString(16).slice(2);
35108
hook._renderers[id] = renderer;
36-
hook.emit('renderer', {id, renderer});
109+
var reactBuildType = detectReactBuildType(renderer);
110+
hook.emit('renderer', {id, renderer, reactBuildType});
37111
return id;
38112
},
39113
_listeners: {},

backend/types.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,18 @@ type DOMNode = {};
4343

4444
export type AnyFn = (...args: Array<any>) => any;
4545

46+
type BundleType =
47+
// PROD
48+
| 0
49+
// DEV
50+
| 1;
51+
4652
export type ReactRenderer = {
4753
// Fiber
4854
findHostInstanceByFiber: (fiber: Object) => ?NativeType,
4955
findFiberByHostInstance: (hostInstance: NativeType) => ?OpaqueNodeHandle,
56+
version: string,
57+
bundleType: BundleType,
5058

5159
// Stack
5260
Reconciler: {

flow/chrome.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ declare var chrome: {
6666
sender: {
6767
tab: {
6868
id: number,
69+
url: string,
6970
},
7071
},
7172
}) => void) => void,
@@ -74,6 +75,7 @@ declare var chrome: {
7475
addListener: (fn: (req: Object, sender: {
7576
tab: {
7677
id: number,
78+
url: string,
7779
},
7880
}) => void) => void,
7981
},
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script src="links.js"></script>
2+
<style>body { font-size: 14px; }</style>
3+
<p style="min-width: 450px">
4+
<b>This page is using the development build of React. &#x1f6a7;</b>
5+
<br />
6+
Open the developer tools, and the React tab will appear to the right.
7+
</p>
8+
<hr />
9+
<p style="min-width: 450px">
10+
Note that the development build is not suitable for production.
11+
<br />
12+
Make sure to <a href="https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build">use the production build</a> before deployment.
13+
</p>

shells/chrome/popups/outdated.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script src="links.js"></script>
2+
<style>body { font-size: 14px; }</style>
3+
<p style="min-width: 450px">
4+
<b>This page is using an outdated version of React. &#8987;</b>
5+
</p>
6+
<hr />
7+
<p style="min-width: 450px">
8+
We recommend updating React to ensure that you receive important bugfixes and performance improvements.
9+
<br />
10+
<br />
11+
You can find the upgrade instructions on the <a href="https://facebook.github.io/react/blog/">React blog</a>.
12+
</p>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script src="links.js"></script>
22
<style>body { font-size: 14px; }</style>
33
<p style="min-width: 450px">
4-
<b>This page is using React. &#x2705;</b>
4+
<b>This page is using the production build of React. &#x2705;</b>
55
<br />
66
Open the developer tools, and the React tab will appear to the right.
77
</p>

shells/chrome/src/GlobalHook.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,17 @@ window.addEventListener('message', function(evt) {
2828
if (evt.source === window && evt.data && evt.data.source === 'react-devtools-detector') {
2929
chrome.runtime.sendMessage({
3030
hasDetectedReact: true,
31+
reactBuildType: evt.data.reactBuildType,
3132
});
3233
}
3334
});
3435

3536
var detectReact = `
36-
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function() {
37-
window.postMessage({source: 'react-devtools-detector'}, '*');
37+
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function(evt) {
38+
window.postMessage({
39+
source: 'react-devtools-detector',
40+
reactBuildType: evt.reactBuildType,
41+
}, '*');
3842
});
3943
`;
4044
var saveNativeValues = `

shells/chrome/src/background.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,28 @@ chrome.runtime.onMessage.addListener((req, sender) => {
7575
// We use browserAction instead of pageAction because this lets us
7676
// display a custom default popup when React is *not* detected.
7777
// It is specified in the manifest.
78+
var reactBuildType = req.reactBuildType;
79+
if (sender.tab.url.indexOf('facebook.github.io/react') !== -1) {
80+
// Cheat: We use the development version on the website because
81+
// it is better for interactive examples. However we're going
82+
// to get misguided bug reports if the extension highlights it
83+
// as using the dev version. We're just going to special case
84+
// our own documentation and cheat. It is acceptable to use dev
85+
// version of React in React docs, but not in any other case.
86+
reactBuildType = 'production';
87+
}
7888
chrome.browserAction.setIcon({
7989
tabId: sender.tab.id,
8090
path: {
81-
'16': 'icons/16-production.png',
82-
'32': 'icons/32-production.png',
83-
'48': 'icons/48-production.png',
84-
'128': 'icons/128-production.png',
91+
'16': 'icons/16-' + reactBuildType + '.png',
92+
'32': 'icons/32-' + reactBuildType + '.png',
93+
'48': 'icons/48-' + reactBuildType + '.png',
94+
'128': 'icons/128-' + reactBuildType + '.png',
8595
},
8696
});
8797
chrome.browserAction.setPopup({
8898
tabId: sender.tab.id,
89-
popup: 'popups/detected.html',
99+
popup: 'popups/' + reactBuildType + '.html',
90100
});
91101
}
92102
});

0 commit comments

Comments
 (0)