Make React Developer Tools work on Chrome extension pages · ngyikp/react-devtools@3bf9cde

3 min read Original article ↗

6 files changed

lines changed

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,57 @@

1+

/**

2+

* Copyright (c) 2015-present, Facebook, Inc.

3+

* All rights reserved.

4+

*

5+

* This source code is licensed under the BSD-style license found in the

6+

* LICENSE file in the root directory of this source tree. An additional grant

7+

* of patent rights can be found in the PATENTS file in the same directory.

8+

*

9+

* @flow

10+

*/

11+

'use strict';

12+
13+

/* global chrome */

14+
15+

module.exports = function() {

16+

// Change this to whatever random string Chrome gives you

17+

var DEVTOOLS_EXTENSION_ID = 'lijlpjbjbiafjcjfhfpjhhjjnligpdda';

18+
19+

// proxy from main page to devtools (via the background page)

20+

var port = chrome.runtime.connect(DEVTOOLS_EXTENSION_ID, {

21+

name: 'content-script_extension_' + window.__REACT_DEVTOOLS_GLOBAL_HOOK__.pageId,

22+

});

23+
24+

port.onMessage.addListener(handleMessageFromDevtools);

25+

port.onDisconnect.addListener(handleDisconnect);

26+

window.addEventListener('message', handleMessageFromPage);

27+
28+

window.postMessage({

29+

source: 'react-devtools-content-script',

30+

hello: true,

31+

}, '*');

32+
33+

function handleMessageFromDevtools(message) {

34+

window.postMessage({

35+

source: 'react-devtools-content-script',

36+

payload: message,

37+

}, '*');

38+

}

39+
40+

function handleMessageFromPage(evt) {

41+

if (evt.data && evt.data.source === 'react-devtools-bridge') {

42+

// console.log('page -> rep -> dev', evt.data);

43+

port.postMessage(evt.data.payload);

44+

}

45+

}

46+
47+

function handleDisconnect() {

48+

window.removeEventListener('message', handleMessageFromPage);

49+

window.postMessage({

50+

source: 'react-devtools-content-script',

51+

payload: {

52+

type: 'event',

53+

evt: 'shutdown',

54+

},

55+

}, '*');

56+

}

57+

};

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,34 @@

1+

/**

2+

* Copyright (c) 2015-present, Facebook, Inc.

3+

* All rights reserved.

4+

*

5+

* This source code is licensed under the BSD-style license found in the

6+

* LICENSE file in the root directory of this source tree. An additional grant

7+

* of patent rights can be found in the PATENTS file in the same directory.

8+

*

9+

* @flow

10+

*/

11+

'use strict';

12+
13+

var contentScript = require('./contentScript');

14+
15+

// Inject a `__REACT_DEVTOOLS_GLOBAL_HOOK__` global so that React can detect that the

16+

// devtools are installed (and skip its suggestion to install the devtools).

17+
18+

var installGlobalHook = require('../../../backend/installGlobalHook.js');

19+

var installRelayHook = require('../../../plugins/Relay/installRelayHook.js');

20+
21+

// We are already running on the page execution scope, there is no need to create

22+

// a <script> tag, plus it wouldn't work under the strict Content Security Policy anyway

23+

installGlobalHook(window);

24+

installRelayHook(window);

25+
26+

window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create;

27+

window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap = Map;

28+

window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap;

29+

window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set;

30+
31+

// Extension popup pages do not have tabId, so we create our own unique identifier

32+

window.__REACT_DEVTOOLS_GLOBAL_HOOK__.pageId = +new Date();

33+
34+

contentScript();

Original file line numberDiff line numberDiff line change

@@ -14,16 +14,41 @@

1414
1515

var ports = {};

1616
17-

chrome.runtime.onConnect.addListener(function(port) {

17+

chrome.runtime.onConnect.addListener(onConnect);

18+
19+

// Enable extension popup pages to be inspected

20+

// TODO: Might be a potential security risk here...

21+

chrome.runtime.onConnectExternal.addListener(onConnect);

22+
23+

function onConnect(port) {

1824

var tab = null;

1925

var name = null;

20-

if (isNumeric(port.name)) {

21-

tab = port.name;

26+

var parts = port.name.split('_');

27+
28+

// Possible outcomes from pages:

29+

// devtools_page_<tabId>

30+

// content-script_page

31+
32+

// Possible outcomes from extensions:

33+

// devtools_extension_<pageId>

34+

// content-script_extension_<pageId>

35+

if (parts[0] === 'devtools') {

2236

name = 'devtools';

23-

installContentScript(+port.name);

24-

} else {

25-

tab = port.sender.tab.id;

37+
38+

tab = +parts[2];

39+

if (parts[1] !== 'extension') {

40+

installContentScript(+parts[2]);

41+

}

42+

} else if (parts[0] === 'content-script') {

43+

if (parts[1] !== 'extension') {

44+

tab = port.sender.tab.id;

45+

} else {

46+

tab = +parts[2];

47+

}

2648

name = 'content-script';

49+

} else {

50+

console.error('Unknown port name ' + port.name);

51+

return;

2752

}

2853
2954

if (!ports[tab]) {

@@ -37,10 +62,6 @@ chrome.runtime.onConnect.addListener(function(port) {

3762

if (ports[tab].devtools && ports[tab]['content-script']) {

3863

doublePipe(ports[tab].devtools, ports[tab]['content-script']);

3964

}

40-

});

41-
42-

function isNumeric(str: string): boolean {

43-

return +str + '' === str;

4465

}

4566
4667

function installContentScript(tabId: number) {

Original file line numberDiff line numberDiff line change

@@ -14,7 +14,7 @@

1414
1515

// proxy from main page to devtools (via the background page)

1616

var port = chrome.runtime.connect({

17-

name: 'content-script',

17+

name: 'content-script_page',

1818

});

1919
2020

port.onMessage.addListener(handleMessageFromDevtools);

Original file line numberDiff line numberDiff line change

@@ -69,27 +69,39 @@ var config: Props = {

6969

},

7070

inject(done) {

7171

inject(chrome.runtime.getURL('build/backend.js'), () => {

72-

var port = chrome.runtime.connect({

73-

name: '' + chrome.devtools.inspectedWindow.tabId,

74-

});

75-

var disconnected = false;

72+

// TODO We should use `chrome.devtools.inspectedWindow.tabId` first and

73+

// then fallback to eval() so this call wouldn't be necessary for most pages

74+

chrome.devtools.inspectedWindow.eval('window.__REACT_DEVTOOLS_GLOBAL_HOOK__.pageId', (pageId, err) => {

75+

var portName;

76+

if (typeof chrome.devtools.inspectedWindow.tabId !== 'undefined') {

77+

portName = 'devtools_page_' + chrome.devtools.inspectedWindow.tabId;

78+

} else {

79+

// Chrome extension popup pages don't have tabId

80+

portName = 'devtools_extension_' + pageId;

81+

}

82+
83+

var port = chrome.runtime.connect({

84+

name: portName,

85+

});

86+

var disconnected = false;

7687
77-

var wall = {

78-

listen(fn) {

79-

port.onMessage.addListener(message => fn(message));

80-

},

81-

send(data) {

82-

if (disconnected) {

83-

return;

84-

}

85-

port.postMessage(data);

86-

},

87-

};

88+

var wall = {

89+

listen(fn) {

90+

port.onMessage.addListener(message => fn(message));

91+

},

92+

send(data) {

93+

if (disconnected) {

94+

return;

95+

}

96+

port.postMessage(data);

97+

},

98+

};

8899
89-

port.onDisconnect.addListener(() => {

90-

disconnected = true;

100+

port.onDisconnect.addListener(() => {

101+

disconnected = true;

102+

});

103+

done(wall, () => port.disconnect());

91104

});

92-

done(wall, () => port.disconnect());

93105

});

94106

},

95107

};

Original file line numberDiff line numberDiff line change

@@ -18,6 +18,7 @@ module.exports = {

1818

background: './src/background.js',

1919

inject: './src/GlobalHook.js',

2020

contentScript: './src/contentScript.js',

21+

reactDevtoolsHelperForChromeExtensions: './src-for-chrome-extension/hook.js',

2122

panel: './src/panel.js',

2223

},

2324

output: {