Skip to content

Commit 61214c6

Browse files
committed
add inspector debug
1 parent 0f9b3fc commit 61214c6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+6849
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module.exports = {
2+
root: true,
3+
extends: '../.eslintrc.js',
4+
rules: {
5+
'no-useless-escape': 'off',
6+
7+
'ember/avoid-leaking-state-in-ember-objects': 'off',
8+
'ember/no-get': 'off',
9+
10+
// TODO: turn this back on when we figure out switching from window.Ember to imports
11+
'ember/new-module-imports': 'off',
12+
},
13+
};
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/* eslint no-console: 0 */
2+
import { onReady } from '@ember/debug/ember-inspector-support/utils/on-ready';
3+
import BaseObject from '../utils/base-object';
4+
5+
export default class BasicAdapter extends BaseObject {
6+
init() {
7+
Promise.resolve(this.connect()).then(() => {
8+
this.onConnectionReady();
9+
}, null);
10+
11+
this._messageCallbacks = [];
12+
}
13+
14+
/**
15+
* Uses the current build's config module to determine
16+
* the environment.
17+
*
18+
* @property environment
19+
* @type {String}
20+
*/
21+
get environment() {
22+
if (!this.__environment) {
23+
this.__environment =
24+
requireModule('@ember/debug/ember-inspector-support/config')['default'].environment;
25+
}
26+
return this.__environment;
27+
}
28+
29+
debug() {
30+
return console.debug(...arguments);
31+
}
32+
33+
log() {
34+
return console.log(...arguments);
35+
}
36+
37+
/**
38+
* A wrapper for `console.warn`.
39+
*
40+
* @method warn
41+
*/
42+
warn() {
43+
return console.warn(...arguments);
44+
}
45+
46+
/**
47+
Used to send messages to EmberExtension
48+
49+
@param {Object} type the message to the send
50+
*/
51+
sendMessage(/* options */) {}
52+
53+
/**
54+
Register functions to be called
55+
when a message from EmberExtension is received
56+
57+
@param {Function} callback
58+
*/
59+
onMessageReceived(callback) {
60+
this._messageCallbacks.push(callback);
61+
}
62+
63+
/**
64+
Inspect a js value or specific DOM node. This usually
65+
means using the current environment's tools
66+
to inspect the node in the DOM.
67+
68+
For example, in chrome, `inspect(node)`
69+
will open the Elements tab in dev tools
70+
and highlight the DOM node.
71+
For functions, it will open the sources tab and goto the definition
72+
@param {Node|Function} node
73+
*/
74+
inspectValue(/* value */) {}
75+
76+
_messageReceived(message) {
77+
this._messageCallbacks.forEach((callback) => {
78+
callback(message);
79+
});
80+
}
81+
82+
/**
83+
* Handle an error caused by EmberDebug.
84+
*
85+
* This function rethrows in development and test envs,
86+
* but warns instead in production.
87+
*
88+
* The idea is to control errors triggered by the inspector
89+
* and make sure that users don't get mislead by inspector-caused
90+
* bugs.
91+
*
92+
* @method handleError
93+
* @param {Error} error
94+
*/
95+
handleError(error) {
96+
if (this.environment === 'production') {
97+
if (error && error instanceof Error) {
98+
error = `Error message: ${error.message}\nStack trace: ${error.stack}`;
99+
}
100+
this.warn(
101+
`Ember Inspector has errored.\n` +
102+
`This is likely a bug in the inspector itself.\n` +
103+
`You can report bugs at https://github.com/emberjs/ember-inspector.\n${error}`
104+
);
105+
} else {
106+
this.warn('EmberDebug has errored:');
107+
throw error;
108+
}
109+
}
110+
111+
/**
112+
113+
A promise that resolves when the connection
114+
with the inspector is set up and ready.
115+
116+
@return {Promise}
117+
*/
118+
connect() {
119+
return new Promise((resolve, reject) => {
120+
onReady(() => {
121+
if (this.isDestroyed) {
122+
reject();
123+
}
124+
this.interval = setInterval(() => {
125+
if (document.documentElement.dataset.emberExtension) {
126+
clearInterval(this.interval);
127+
resolve();
128+
}
129+
}, 10);
130+
});
131+
});
132+
}
133+
134+
willDestroy() {
135+
super.willDestroy();
136+
clearInterval(this.interval);
137+
}
138+
139+
_isReady = false;
140+
_pendingMessages = [];
141+
142+
send(options) {
143+
if (this._isReady) {
144+
this.sendMessage(...arguments);
145+
} else {
146+
this._pendingMessages.push(options);
147+
}
148+
}
149+
150+
/**
151+
Called when the connection is set up.
152+
Flushes the pending messages.
153+
*/
154+
onConnectionReady() {
155+
// Flush pending messages
156+
const messages = this._pendingMessages;
157+
messages.forEach((options) => this.sendMessage(options));
158+
messages.length = 0;
159+
this._isReady = true;
160+
}
161+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import BasicAdapter from './basic';
2+
3+
export default class extends BasicAdapter {
4+
init() {
5+
super.init();
6+
this._listen();
7+
}
8+
9+
sendMessage(options) {
10+
options = options || {};
11+
window.emberInspector.w.postMessage(options, window.emberInspector.url);
12+
}
13+
14+
_listen() {
15+
window.addEventListener('message', (e) => {
16+
if (e.origin !== window.emberInspector.url) {
17+
return;
18+
}
19+
const message = e.data;
20+
if (message.from === 'devtools') {
21+
this._messageReceived(message);
22+
}
23+
});
24+
25+
window.onunload = () => {
26+
this.sendMessage({
27+
unloading: true,
28+
});
29+
};
30+
}
31+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import WebExtension from './web-extension';
2+
export default class extends WebExtension {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* eslint no-empty:0 */
2+
import WebExtension from './web-extension';
3+
4+
export default class extends WebExtension {
5+
debug() {
6+
// WORKAROUND: temporarily workaround issues with firebug console object:
7+
// - https://github.com/tildeio/ember-extension/issues/94
8+
// - https://github.com/firebug/firebug/pull/109
9+
// - https://code.google.com/p/fbug/issues/detail?id=7045
10+
try {
11+
super.debug(...arguments);
12+
} catch (e) {}
13+
}
14+
15+
log() {
16+
// WORKAROUND: temporarily workaround issues with firebug console object:
17+
// - https://github.com/tildeio/ember-extension/issues/94
18+
// - https://github.com/firebug/firebug/pull/109
19+
// - https://code.google.com/p/fbug/issues/detail?id=7045
20+
try {
21+
super.log(...arguments);
22+
} catch (e) {}
23+
}
24+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import BasicAdapter from './basic';
2+
import { typeOf } from '@ember/debug/ember-inspector-support/utils/type-check';
3+
4+
import Ember from '@ember/debug/ember-inspector-support/utils/ember';
5+
import { run } from '@ember/debug/ember-inspector-support/utils/ember/runloop';
6+
7+
const { isArray } = Array;
8+
const { keys } = Object;
9+
10+
export default class extends BasicAdapter {
11+
init() {
12+
this._channel = new MessageChannel();
13+
this._chromePort = this._channel?.port1;
14+
super.init();
15+
}
16+
17+
connect() {
18+
const channel = this._channel;
19+
return super.connect().then(() => {
20+
window.postMessage('debugger-client', '*', [channel.port2]);
21+
this._listen();
22+
}, null);
23+
}
24+
25+
sendMessage(options = {}) {
26+
// If prototype extensions are disabled, `Ember.A()` arrays
27+
// would not be considered native arrays, so it's not possible to
28+
// "clone" them through postMessage unless they are converted to a
29+
// native array.
30+
options = deepClone(options);
31+
try {
32+
this._chromePort.postMessage(options);
33+
} catch (e) {
34+
console.log('failed to send message', e);
35+
}
36+
}
37+
38+
/**
39+
* Open the devtools "Elements" and select an DOM node.
40+
*
41+
* @param {Node|Function} value The DOM node to select
42+
*/
43+
inspectValue(value) {
44+
// NOTE:
45+
//
46+
// Basically, we are just trying to call `inspect(node)` here.
47+
// However, `inspect` is a special function that is in the global
48+
// scope but not on the global object (i.e. `window.inspect`) does
49+
// not work. This sometimes causes problems, because, e.g. if the
50+
// page has a div with the ID `inspect`, `window.inspect` will point
51+
// to that div and shadow the "global" inspect function with no way
52+
// to get it back. That causes "`inspect` is not a function" errors.
53+
//
54+
// As it turns out, however, when the extension page evals, the
55+
// `inspect` function does not get shadowed. So, we can ask the
56+
// inspector extension page to call that function for us, using
57+
// `inspected.Window.eval('inspect(node)')`.
58+
//
59+
// However, since we cannot just send the DOM node directly to the
60+
// extension, we will have to store it in a temporary global variable
61+
// so that the extension can find it.
62+
63+
let name = `__EMBER_INSPECTOR_${(Math.random() * 100000000).toFixed(0)}`;
64+
65+
window[name] = value;
66+
67+
this.namespace.port.send('view:inspectJSValue', { name });
68+
}
69+
70+
_listen() {
71+
let chromePort = this._chromePort;
72+
73+
chromePort.addEventListener('message', (event) => {
74+
const message = event.data;
75+
76+
// We should generally not be run-wrapping here. Starting a runloop in
77+
// ember-debug will cause the inspected app to revalidate/rerender. We
78+
// are generally not intending to cause changes to the rendered output
79+
// of the app, so this is generally unnecessary, and in big apps this
80+
// could be quite slow. There is nothing special about the `view:*`
81+
// messages – I (GC) just happened to have reviewed all of them recently
82+
// and can be quite sure that they don't need the runloop. We should
83+
// audit the rest of them and see if we can remove the else branch. I
84+
// think we most likely can. In the limited cases (if any) where the
85+
// runloop is needed, the callback code should just do the wrapping
86+
// themselves.
87+
if (message.type.startsWith('view:')) {
88+
this._messageReceived(message);
89+
} else {
90+
run(() => {
91+
this._messageReceived(message);
92+
});
93+
}
94+
});
95+
96+
chromePort.start();
97+
}
98+
}
99+
100+
// On some older Ember version `Ember.ENV.EXTEND_PROTOTYPES` is not
101+
// guarenteed to be an object. While this code only support 3.4+ (all
102+
// of which normalizes `EXTEND_PROTOTYPES` for us), startup-wrapper.js
103+
// eagerly require/load ember-debug modules, which ultimately causes
104+
// this top-level code to run, even we are going to pick a different
105+
// adapter later. See GH #1114.
106+
const HAS_ARRAY_PROTOTYPE_EXTENSIONS = (() => {
107+
try {
108+
return Ember.ENV.EXTEND_PROTOTYPES.Array === true;
109+
} catch (e) {
110+
return false;
111+
}
112+
})();
113+
114+
let deepClone;
115+
116+
if (HAS_ARRAY_PROTOTYPE_EXTENSIONS) {
117+
deepClone = function deepClone(item) {
118+
return item;
119+
};
120+
} else {
121+
/**
122+
* Recursively clones all arrays. Needed because Chrome
123+
* refuses to clone Ember Arrays when extend prototypes is disabled.
124+
*
125+
* If the item passed is an array, a clone of the array is returned.
126+
* If the item is an object or an array, or array properties/items are cloned.
127+
*
128+
* @param {Mixed} item The item to clone
129+
* @return {Mixed}
130+
*/
131+
deepClone = function deepClone(item) {
132+
let clone = item;
133+
if (isArray(item)) {
134+
clone = new Array(item.length);
135+
item.forEach((child, key) => {
136+
clone[key] = deepClone(child);
137+
});
138+
} else if (item && typeOf(item) === 'object') {
139+
clone = {};
140+
keys(item).forEach((key) => {
141+
clone[key] = deepClone(item[key]);
142+
});
143+
}
144+
return clone;
145+
};
146+
}

0 commit comments

Comments
 (0)