electron-devtools-vendor
Version:
<div align="center"> <h2>electron-devtools-vendor</h2> <img alt="MIT" src="https://img.shields.io/github/license/BlackHole1/electron-devtools-vendor?color=9cf&style=flat-square"> <img alt="GitHub repo size" src="https://img.shields.io/github/r
1,765 lines (1,561 loc) • 272 kB
JavaScript
(function(adapter, env) {
let define = window.define, requireModule = window.requireModule;
if (typeof define !== 'function' || typeof requireModule !== 'function') {
(function() {
let registry = {}, seen = {};
define = function(name, deps, callback) {
if (arguments.length < 3) {
callback = deps;
deps = [];
}
registry[name] = { deps, callback };
};
requireModule = function(name) {
if (seen[name]) { return seen[name]; }
seen[name] = {};
let mod = registry[name];
if (!mod) {
throw new Error(`Module: '${name}' not found.`);
}
let deps = mod.deps;
let callback = mod.callback;
let reified = [];
let exports;
for (let i = 0, l = deps.length; i < l; i++) {
if (deps[i] === 'exports') {
reified.push(exports = {});
} else {
reified.push(requireModule(deps[i]));
}
}
let value = callback.apply(this, reified);
seen[name] = exports || value;
return seen[name];
};
define.registry = registry;
define.seen = seen;
})();
}
define('ember-debug/adapters/basic', ['exports', 'ember-debug/utils/on-ready'], function (exports, _onReady) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
/* globals requireModule */
/* eslint no-console: 0 */
const Ember = window.Ember;
const { A, computed, RSVP, Object: EmberObject } = Ember;
const { Promise, resolve } = RSVP;
exports.default = EmberObject.extend({
init() {
resolve(this.connect(), 'ember-inspector').then(() => {
this.onConnectionReady();
}, null, 'ember-inspector');
},
/**
* Uses the current build's config module to determine
* the environment.
*
* @property environment
* @type {String}
*/
environment: computed(function () {
return requireModule('ember-debug/config')['default'].environment;
}),
debug() {
return console.debug(...arguments);
},
log() {
return console.log(...arguments);
},
/**
* A wrapper for `console.warn`.
*
* @method warn
*/
warn() {
return console.warn(...arguments);
},
/**
Used to send messages to EmberExtension
@param {Object} type the message to the send
*/
sendMessage() /* options */{},
/**
Register functions to be called
when a message from EmberExtension is received
@param {Function} callback
*/
onMessageReceived(callback) {
this.get('_messageCallbacks').pushObject(callback);
},
/**
Inspect a specific element. This usually
means using the current environment's tools
to inspect the element in the DOM.
For example, in chrome, `inspect(elem)`
will open the Elements tab in dev tools
and highlight the element.
@param {DOM Element} elem
*/
inspectElement() /* elem */{},
_messageCallbacks: computed(function () {
return A();
}),
_messageReceived(message) {
this.get('_messageCallbacks').forEach(callback => {
callback(message);
});
},
/**
* Handle an error caused by EmberDebug.
*
* This function rethrows in development and test envs,
* but warns instead in production.
*
* The idea is to control errors triggered by the inspector
* and make sure that users don't get mislead by inspector-caused
* bugs.
*
* @method handleError
* @param {Error} error
*/
handleError(error) {
if (this.get('environment') === 'production') {
if (error && error instanceof Error) {
error = `Error message: ${error.message}\nStack trace: ${error.stack}`;
}
this.warn(`Ember Inspector has errored.\n` + `This is likely a bug in the inspector itself.\n` + `You can report bugs at https://github.com/emberjs/ember-inspector.\n${error}`);
} else {
this.warn('EmberDebug has errored:');
throw error;
}
},
/**
A promise that resolves when the connection
with the inspector is set up and ready.
@return {Promise}
*/
connect() {
return new Promise((resolve, reject) => {
(0, _onReady.onReady)(() => {
if (this.isDestroyed) {
reject();
}
this.interval = setInterval(() => {
if (document.documentElement.dataset.emberExtension) {
clearInterval(this.interval);
resolve();
}
}, 10);
});
}, 'ember-inspector');
},
willDestroy() {
this._super();
clearInterval(this.interval);
},
_isReady: false,
_pendingMessages: computed(function () {
return A();
}),
send(options) {
if (this._isReady) {
this.sendMessage(...arguments);
} else {
this.get('_pendingMessages').push(options);
}
},
/**
Called when the connection is set up.
Flushes the pending messages.
*/
onConnectionReady() {
// Flush pending messages
const messages = this.get('_pendingMessages');
messages.forEach(options => this.sendMessage(options));
messages.clear();
this._isReady = true;
}
});
});
define('ember-debug/adapters/bookmarklet', ['exports', 'ember-debug/adapters/basic'], function (exports, _basic) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _basic.default.extend({
init() {
this._super();
this._listen();
},
sendMessage(options) {
options = options || {};
window.emberInspector.w.postMessage(options, window.emberInspector.url);
},
_listen() {
window.addEventListener('message', e => {
if (e.origin !== window.emberInspector.url) {
return;
}
const message = e.data;
if (message.from === 'devtools') {
this._messageReceived(message);
}
});
window.onunload = () => {
this.sendMessage({
unloading: true
});
};
}
});
});
define("ember-debug/adapters/chrome", ["exports", "ember-debug/adapters/web-extension"], function (exports, _webExtension) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _webExtension.default.extend();
});
define("ember-debug/adapters/firefox", ["exports", "ember-debug/adapters/web-extension"], function (exports, _webExtension) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = _webExtension.default.extend({
debug() {
// WORKAROUND: temporarily workaround issues with firebug console object:
// - https://github.com/tildeio/ember-extension/issues/94
// - https://github.com/firebug/firebug/pull/109
// - https://code.google.com/p/fbug/issues/detail?id=7045
try {
this._super(...arguments);
} catch (e) {}
},
log() {
// WORKAROUND: temporarily workaround issues with firebug console object:
// - https://github.com/tildeio/ember-extension/issues/94
// - https://github.com/firebug/firebug/pull/109
// - https://code.google.com/p/fbug/issues/detail?id=7045
try {
this._super(...arguments);
} catch (e) {}
}
});
});
define('ember-debug/adapters/web-extension', ['exports', 'ember-debug/adapters/basic'], function (exports, _basic) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
const Ember = window.Ember;
const { run, typeOf } = Ember;
const { isArray } = Array;
const { keys } = Object;
exports.default = _basic.default.extend({
init() {
this.set('_channel', new MessageChannel());
this.set('_chromePort', this.get('_channel.port1'));
this._super(...arguments);
},
connect() {
const channel = this.get('_channel');
return this._super(...arguments).then(() => {
window.postMessage('debugger-client', '*', [channel.port2]);
this._listen();
}, null, 'ember-inspector');
},
sendMessage(options = {}) {
// If prototype extensions are disabled, `Ember.A()` arrays
// would not be considered native arrays, so it's not possible to
// "clone" them through postMessage unless they are converted to a
// native array.
options = deepClone(options);
this.get('_chromePort').postMessage(options);
},
/**
* Open the devtools "Elements" and select an element.
*
* NOTE:
* This method was supposed to call `inspect` which is a Chrome specific function
* that can either be called from the console or from code evaled using `inspectedWindow.eval`
* (which is how this code is executed). See https://developer.chrome.com/extensions/devtools#evaluating-js.
* However for some reason Chrome 52+ has started throwing an Error that `inspect`
* is not a function when called from this code. The current workaround is to
* message the Ember Ibspector asking it to execute `inspected.Window.eval('inspect(element)')`
* for us.
*
* @param {HTMLElement} elem The element to select
*/
inspectElement(elem) {
/* inspect(elem); */
this.get('namespace.port').send('view:inspectDOMElement', {
elementSelector: `#${elem.getAttribute('id')}`
});
},
_listen() {
let chromePort = this.get('_chromePort');
chromePort.addEventListener('message', event => {
const message = event.data;
run(() => {
this._messageReceived(message);
});
});
chromePort.start();
}
});
/**
* Recursively clones all arrays. Needed because Chrome
* refuses to clone Ember Arrays when extend prototypes is disabled.
*
* If the item passed is an array, a clone of the array is returned.
* If the item is an object or an array, or array properties/items are cloned.
*
* @param {Mixed} item The item to clone
* @return {Mixed}
*/
function deepClone(item) {
let clone = item;
if (isArray(item)) {
clone = new Array(item.length);
item.forEach((child, key) => {
clone[key] = deepClone(child);
});
} else if (item && typeOf(item) === 'object') {
clone = {};
keys(item).forEach(key => {
clone[key] = deepClone(item[key]);
});
}
return clone;
}
});
define('ember-debug/adapters/websocket', ['exports', 'ember-debug/adapters/basic', 'ember-debug/utils/on-ready'], function (exports, _basic, _onReady) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
const Ember = window.Ember;
const { computed, run, RSVP: { Promise } } = Ember;
exports.default = _basic.default.extend({
sendMessage(options = {}) {
this.get('socket').emit('emberInspectorMessage', options);
},
socket: computed(function () {
return window.EMBER_INSPECTOR_CONFIG.remoteDebugSocket;
}),
_listen() {
this.get('socket').on('emberInspectorMessage', message => {
run(() => {
this._messageReceived(message);
});
});
},
_disconnect() {
this.get('socket').removeAllListeners("emberInspectorMessage");
},
connect() {
return new Promise((resolve, reject) => {
(0, _onReady.onReady)(() => {
if (this.isDestroyed) {
reject();
}
const EMBER_INSPECTOR_CONFIG = window.EMBER_INSPECTOR_CONFIG;
if (typeof EMBER_INSPECTOR_CONFIG === 'object' && EMBER_INSPECTOR_CONFIG.remoteDebugSocket) {
resolve();
}
});
}).then(() => {
this._listen();
});
},
willDestroy() {
this._disconnect();
}
});
});
define('ember-debug/container-debug', ['exports', 'ember-debug/mixins/port-mixin'], function (exports, _portMixin) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
const Ember = window.Ember;
const { Object: EmberObject, computed } = Ember;
const { readOnly } = computed;
exports.default = EmberObject.extend(_portMixin.default, {
namespace: null,
objectInspector: readOnly('namespace.objectInspector'),
container: computed('namespace.owner', function () {
// should update this to use real owner API
return this.get('namespace.owner.__container__');
}),
portNamespace: 'container',
TYPES_TO_SKIP: computed(function () {
return ['component-lookup', 'container-debug-adapter', 'resolver-for-debugging', 'event_dispatcher'];
}),
typeFromKey(key) {
return key.split(':').shift();
},
nameFromKey(key) {
return key.split(':').pop();
},
shouldHide(type) {
return type[0] === '-' || this.get('TYPES_TO_SKIP').indexOf(type) !== -1;
},
instancesByType() {
let key;
let instancesByType = {};
let cache = this.get('container').cache;
// Detect if InheritingDict (from Ember < 1.8)
if (typeof cache.dict !== 'undefined' && typeof cache.eachLocal !== 'undefined') {
cache = cache.dict;
}
for (key in cache) {
const type = this.typeFromKey(key);
if (this.shouldHide(type)) {
continue;
}
if (instancesByType[type] === undefined) {
instancesByType[type] = [];
}
instancesByType[type].push({
fullName: key,
instance: cache[key]
});
}
return instancesByType;
},
getTypes() {
let key;
let types = [];
const instancesByType = this.instancesByType();
for (key in instancesByType) {
types.push({ name: key, count: instancesByType[key].length });
}
return types;
},
getInstances(type) {
const instances = this.instancesByType()[type];
if (!instances) {
return null;
}
return instances.map(item => ({
name: this.nameFromKey(item.fullName),
fullName: item.fullName,
inspectable: this.get('objectInspector').canSend(item.instance)
}));
},
messages: {
getTypes() {
this.sendMessage('types', {
types: this.getTypes()
});
},
getInstances(message) {
let instances = this.getInstances(message.containerType);
if (instances) {
this.sendMessage('instances', {
instances,
status: 200
});
} else {
this.sendMessage('instances', {
status: 404
});
}
},
sendInstanceToConsole(message) {
const instance = this.get('container').lookup(message.name);
this.get('objectToConsole').sendValueToConsole(instance);
}
}
});
});
define('ember-debug/data-debug', ['exports', 'ember-debug/mixins/port-mixin'], function (exports, _portMixin) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
const Ember = window.Ember;
const { Object: EmberObject, computed, guidFor, A, set } = Ember;
const { alias } = computed;
exports.default = EmberObject.extend(_portMixin.default, {
init() {
this._super();
this.sentTypes = {};
this.sentRecords = {};
},
releaseTypesMethod: null,
releaseRecordsMethod: null,
/* eslint-disable ember/no-side-effects */
adapter: computed('namespace.owner', function () {
const owner = this.get('namespace.owner');
// dataAdapter:main is deprecated
let adapter = this._resolve('data-adapter:main') && owner.lookup('data-adapter:main');
// column limit is now supported at the inspector level
if (adapter) {
set(adapter, 'attributeLimit', 100);
return adapter;
}
}),
/* eslint-enable ember/no-side-effects */
_resolve(name) {
const owner = this.get('namespace.owner');
return owner.resolveRegistration(name);
},
namespace: null,
port: alias('namespace.port'),
objectInspector: alias('namespace.objectInspector'),
portNamespace: 'data',
modelTypesAdded(types) {
let typesToSend;
typesToSend = types.map(type => this.wrapType(type));
this.sendMessage('modelTypesAdded', {
modelTypes: typesToSend
});
},
modelTypesUpdated(types) {
let typesToSend = types.map(type => this.wrapType(type));
this.sendMessage('modelTypesUpdated', {
modelTypes: typesToSend
});
},
wrapType(type) {
const objectId = guidFor(type.object);
this.sentTypes[objectId] = type;
return {
columns: type.columns,
count: type.count,
name: type.name,
objectId
};
},
recordsAdded(recordsReceived) {
let records = recordsReceived.map(record => this.wrapRecord(record));
this.sendMessage('recordsAdded', { records });
},
recordsUpdated(recordsReceived) {
let records = recordsReceived.map(record => this.wrapRecord(record));
this.sendMessage('recordsUpdated', { records });
},
recordsRemoved(index, count) {
this.sendMessage('recordsRemoved', { index, count });
},
wrapRecord(record) {
const objectId = guidFor(record.object);
let columnValues = {};
let searchKeywords = [];
this.sentRecords[objectId] = record;
// make objects clonable
for (let i in record.columnValues) {
columnValues[i] = this.get('objectInspector').inspect(record.columnValues[i]);
}
// make sure keywords can be searched and clonable
searchKeywords = A(record.searchKeywords).filter(keyword => typeof keyword === 'string' || typeof keyword === 'number');
return {
columnValues,
searchKeywords,
filterValues: record.filterValues,
color: record.color,
objectId
};
},
releaseTypes() {
if (this.releaseTypesMethod) {
this.releaseTypesMethod();
this.releaseTypesMethod = null;
this.sentTypes = {};
}
},
releaseRecords() {
if (this.releaseRecordsMethod) {
this.releaseRecordsMethod();
this.releaseRecordsMethod = null;
this.sentRecords = {};
}
},
willDestroy() {
this._super();
this.releaseRecords();
this.releaseTypes();
},
messages: {
checkAdapter() {
this.sendMessage('hasAdapter', { hasAdapter: !!this.get('adapter') });
},
getModelTypes() {
this.releaseTypes();
this.releaseTypesMethod = this.get('adapter').watchModelTypes(types => {
this.modelTypesAdded(types);
}, types => {
this.modelTypesUpdated(types);
});
},
releaseModelTypes() {
this.releaseTypes();
},
getRecords(message) {
const type = this.sentTypes[message.objectId];
this.releaseRecords();
let typeOrName;
if (this.get('adapter.acceptsModelName')) {
// Ember >= 1.3
typeOrName = type.name;
}
let releaseMethod = this.get('adapter').watchRecords(typeOrName, recordsReceived => {
this.recordsAdded(recordsReceived);
}, recordsUpdated => {
this.recordsUpdated(recordsUpdated);
}, (...args) => {
this.recordsRemoved(...args);
});
this.releaseRecordsMethod = releaseMethod;
},
releaseRecords() {
this.releaseRecords();
},
inspectModel(message) {
this.get('objectInspector').sendObject(this.sentRecords[message.objectId].object);
},
getFilters() {
this.sendMessage('filters', {
filters: this.get('adapter').getFilters()
});
}
}
});
});
define('ember-debug/deprecation-debug', ['exports', 'ember-debug/mixins/port-mixin', 'ember-debug/libs/source-map'], function (exports, _portMixin, _sourceMap) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
const Ember = window.Ember;
const { Debug, Object: EmberObject, computed, guidFor, run, RSVP, A } = Ember;
const { resolve, all } = RSVP;
const { readOnly } = computed;
const { registerDeprecationHandler } = Debug;
exports.default = EmberObject.extend(_portMixin.default, {
portNamespace: 'deprecation',
adapter: readOnly('port.adapter'),
sourceMap: computed(function () {
return _sourceMap.default.create();
}),
emberCliConfig: readOnly('namespace.generalDebug.emberCliConfig'),
init() {
this._super();
this.deprecations = A();
this.deprecationsToSend = A();
this.groupedDeprecations = {};
this.options = {
toggleDeprecationWorkflow: false
};
this.handleDeprecations();
},
/**
* Checks if ember-cli and looks for source maps.
*/
fetchSourceMap(stackStr) {
if (this.get('emberCliConfig') && this.get('emberCliConfig.environment') === 'development') {
return this.get('sourceMap').map(stackStr).then(mapped => {
if (mapped && mapped.length > 0) {
let source = mapped.find(item => item.source && !!item.source.match(new RegExp(this.get('emberCliConfig.modulePrefix'))));
if (source) {
source.found = true;
} else {
source = mapped.get('firstObject');
source.found = false;
}
return source;
}
}, null, 'ember-inspector');
} else {
return resolve(null, 'ember-inspector');
}
},
sendPending() {
if (this.isDestroyed) {
return;
}
let deprecations = A();
let promises = all(this.get('deprecationsToSend').map(deprecation => {
let obj;
let promise = resolve(undefined, 'ember-inspector');
let grouped = this.get('groupedDeprecations');
this.get('deprecations').pushObject(deprecation);
const id = guidFor(deprecation.message);
obj = grouped[id];
if (obj) {
obj.count++;
obj.url = obj.url || deprecation.url;
} else {
obj = deprecation;
obj.count = 1;
obj.id = id;
obj.sources = A();
grouped[id] = obj;
}
let found = obj.sources.findBy('stackStr', deprecation.stackStr);
if (!found) {
let stackStr = deprecation.stackStr;
promise = this.fetchSourceMap(stackStr).then(map => {
obj.sources.pushObject({ map, stackStr });
if (map) {
obj.hasSourceMap = true;
}
}, null, 'ember-inspector');
}
return promise.then(() => {
delete obj.stackStr;
deprecations.addObject(obj);
}, null, 'ember-inspector');
}));
promises.then(() => {
this.sendMessage('deprecationsAdded', { deprecations });
this.get('deprecationsToSend').clear();
this.sendCount();
}, null, 'ember-inspector');
},
sendCount() {
if (this.isDestroyed) {
return;
}
this.sendMessage('count', {
count: this.get('deprecations.length') + this.get('deprecationsToSend.length')
});
},
messages: {
watch() {
this._watching = true;
let grouped = this.get('groupedDeprecations');
let deprecations = [];
for (let i in grouped) {
if (!grouped.hasOwnProperty(i)) {
continue;
}
deprecations.push(grouped[i]);
}
this.sendMessage('deprecationsAdded', {
deprecations
});
this.sendPending();
},
sendStackTraces(message) {
let deprecation = message.deprecation;
deprecation.sources.forEach(source => {
let stack = source.stackStr;
stack = stack.split('\n');
stack.unshift(`Ember Inspector (Deprecation Trace): ${deprecation.message || ''}`);
this.get('adapter').log(stack.join('\n'));
});
},
getCount() {
this.sendCount();
},
clear() {
run.cancel(this.debounce);
this.get('deprecations').clear();
this.set('groupedDeprecations', {});
this.sendCount();
},
release() {
this._watching = false;
},
setOptions({ options }) {
this.options.toggleDeprecationWorkflow = options.toggleDeprecationWorkflow;
}
},
willDestroy() {
run.cancel(this.debounce);
return this._super(...arguments);
},
handleDeprecations() {
registerDeprecationHandler((message, options, next) => {
/* global __fail__*/
let error;
// When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome
try {
__fail__.fail();
} catch (e) {
error = e;
}
let stack;
let stackStr = '';
if (error.stack) {
// var stack;
if (error['arguments']) {
// Chrome
stack = error.stack.replace(/^\s+at\s+/gm, '').replace(/^([^\(]+?)([\n$])/gm, '{anonymous}($1)$2').replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}($1)').split('\n');
stack.shift();
} else {
// Firefox
stack = error.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n');
}
stackStr = `\n ${stack.slice(2).join('\n ')}`;
}
let url;
if (options && typeof options === 'object') {
url = options.url;
}
const deprecation = { message, stackStr, url };
// For ember-debug testing we usually don't want
// to catch deprecations
if (!this.get('namespace').IGNORE_DEPRECATIONS) {
this.get('deprecationsToSend').pushObject(deprecation);
run.cancel(this.debounce);
if (this._watching) {
this.debounce = run.debounce(this, 'sendPending', 100);
} else {
this.debounce = run.debounce(this, 'sendCount', 100);
}
if (!this._warned) {
this.get('adapter').warn('Deprecations were detected, see the Ember Inspector deprecations tab for more details.');
this._warned = true;
}
}
if (this.options.toggleDeprecationWorkflow) {
next(message, options);
}
});
}
});
});
define('ember-debug/general-debug', ['exports', 'ember-debug/mixins/port-mixin'], function (exports, _portMixin) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
const Ember = window.Ember; /* eslint no-empty:0 */
const { Object: EmberObject } = Ember;
let { libraries } = Ember;
/**
* Class that handles gathering general information of the inspected app.
* ex:
* - Determines if the app was booted
* - Gathers the libraries. Found in the info tab of the inspector.
* - Gathers ember-cli configuration information from the meta tags.
*
* @module ember-debug/general-debug
*/
exports.default = EmberObject.extend(_portMixin.default, {
/**
* Fetches the ember-cli configuration info and sets them on
* the `emberCliConfig` property.
*/
init() {
this._super(...arguments);
let found = findMetaTag('name', /environment$/);
if (found) {
try {
let config = JSON.parse(unescape(found.getAttribute('content')));
this.set('emberCliConfig', config);
} catch (e) {}
}
},
/**
* Passed on creation.
*
* @type {EmberDebug}
*/
namespace: null,
/**
* Used by the PortMixin
*
* @type {String}
*/
portNamespace: 'general',
/**
* Set on creation.
* Contains ember-cli configuration info.
*
* Info used to determine the file paths of an ember-cli app.
*
* @return {Object}
* {String} environment ex: 'development'
* {String} modulePrefix ex: 'my-app'
* {String} podModulePrefix ex: 'my-app/pods'
* {Boolean} usePodsByDefault
*/
emberCliConfig: null,
/**
* Sends a reply back indicating if the app has been booted.
*
* `__inspector__booted` is a property set on the application instance
* when the ember-debug is inserted into the target app.
* see: startup-wrapper.
*/
sendBooted() {
this.sendMessage('applicationBooted', {
booted: this.get('namespace.owner.__inspector__booted')
});
},
/**
* Sends a reply back indicating that ember-debug has been reset.
* We need to reset ember-debug to remove state between tests.
*/
sendReset() {
this.sendMessage('reset');
},
messages: {
/**
* Called from the inspector to check if the inspected app has been booted.
*/
applicationBooted() {
this.sendBooted();
},
/**
* Called from the inspector to fetch the libraries that are displayed in
* the info tab.
*/
getLibraries() {
this.sendMessage('libraries', { libraries: libraries._registry });
},
/**
* Called from the inspector to refresh the inspected app.
* Used in case the inspector was opened late and therefore missed capturing
* all info.
*/
refresh() {
window.location.reload();
}
}
});
/**
* Finds a meta tag by searching through a certain meta attribute.
*
* @param {String} attribute
* @param {RegExp} regExp
* @return {Element}
*/
function findMetaTag(attribute, regExp = /.*/) {
let metas = document.querySelectorAll(`meta[${attribute}]`);
for (let i = 0; i < metas.length; i++) {
let match = metas[i].getAttribute(attribute).match(regExp);
if (match) {
return metas[i];
}
}
return null;
}
});
define('ember-debug/libs/glimmer-tree', ['exports', 'ember-debug/utils/name-functions'], function (exports, _nameFunctions) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
/**
* This class contains functionality related to for Ember versions
* using Glimmer 2 (Ember >= 2.9):
*
* It has the following main responsibilities:
*
* - Building the view tree.
* - Highlighting components/outlets when the view tree is hovered.
* - Highlighting components/outlets when the views themselves are hovered.
* - Finding the model of a specific outlet/component.
*
* The view tree is a hierarchy of nodes (optionally) containing the following info:
* - name
* - template
* - id
* - view class
* - duration
* - tag name
* - model
* - controller
* - type
*
* Once the view tree is generated it can be sent to the Ember Inspector to be displayed.
*
* @class GlimmerTree
*/
const Ember = window.Ember;
const { Object: EmberObject, typeOf, isNone, Controller, ViewUtils, get, A } = Ember;
const { getRootViews, getChildViews, getViewBoundingClientRect } = ViewUtils;
exports.default = class {
/**
* Sets up the initial options.
*
* @method constructor
* @param {Object} options
* - {owner} owner The Ember app's owner.
* - {Function} retainObject Called to retain an object for future inspection.
* - {Object} options Options whether to show components or not.
* - {Object} durations Hash containing time to render per view id.
* - {Function} highlightRange Called to highlight a range of elements.
* - {Object} ObjectInspector Used to inspect models.
* - {Object} viewRegistry Hash containing all currently rendered components by id.
*/
constructor({
owner,
retainObject,
options,
durations,
highlightRange,
objectInspector,
viewRegistry
}) {
this.owner = owner;
this.retainObject = retainObject;
this.options = options;
this.durations = durations;
this.highlightRange = highlightRange;
this.objectInspector = objectInspector;
this.viewRegistry = viewRegistry;
}
/**
* @method updateOptions
* @param {Object} options
*/
updateOptions(options) {
this.options = options;
}
/**
* @method updateDurations
* @param {Object} durations
*/
updateDurations(durations) {
this.duations = durations;
}
/**
* Builds the view tree. The view tree may or may not contain
* components depending on the current options.
*
* The view tree has the top level outlet as the root of the tree.
* The format is:
* {
* value: |hash of properties|,
* children: [
* {
* value: |hash of properties|,
* children: []
* },
* {
* value: |hash of properties|,
* children: [...]
* }]
* }
*
* We are building the tree is by doing the following steps:
* - Build the outlet tree by walking the outlet state.
* - Build several component trees, each tree belonging to one controller.
* - Assign each controller-specific component tree as a child of the outlet corresponding
* to that specific controller.
*
* @method build
* @return {Object} The view tree
*/
build() {
if (this.getRoot()) {
let outletTree = this.buildOutletTree();
let componentTrees = this.options.components ? this.buildComponentTrees(outletTree) : [];
return this.addComponentsToOutlets(outletTree, componentTrees);
}
}
/**
* Starts with the root and walks the tree till
* the leaf outlets. The format is:
* {
* value: |inspected outlet|,
* children:
* [
* {
* value: |inspected outlet|,
* children: [...]
* }
* ]
* }
*
* @method buildOutletTree
* @return {Object} Tree of inspected outlets
*/
buildOutletTree() {
let outletTree = this.makeOutletTree(this.getApplicationOutlet());
// set root element's id
let rootElement = this.elementForRoot();
if (rootElement instanceof HTMLElement) {
outletTree.value.elementId = rootElement.getAttribute('id');
}
outletTree.value.tagName = 'div';
return outletTree;
}
/**
* The recursive part of building the outlet tree.
*
* Return format:
* {
* value: |inspected outlet|
* controller: |controller instance|
* children: [...]
* }
*
* @method makeOutletTree
* @param {Object} outletState
* @return {Object} The inspected outlet tree
*/
makeOutletTree(outletState) {
let { render: { controller }, outlets } = outletState;
let node = { value: this.inspectOutlet(outletState), controller, children: [] };
for (let key in outlets) {
// disconnectOutlet() resets the controller value as undefined (https://github.com/emberjs/ember.js/blob/v2.6.2/packages/ember-routing/lib/system/route.js#L2048).
// So skip building the tree, if the outletState doesn't have a controller.
if (this.controllerForOutlet(outlets[key])) {
node.children.push(this.makeOutletTree(outlets[key]));
}
}
return node;
}
/**
* Builds the component trees. Each tree corresponds to one controller.
* A component's controller is determined by its target (or ancestor's target).
*
* Has the following format:
* {
* controller: |The controller instance|,
* components: [|component tree|]
* }
*
* @method buildComponentTrees
* @param {Object} outletTree
* @return {Array} The component tree
*/
buildComponentTrees(outletTree) {
let controllers = this.controllersFromOutletTree(outletTree);
return controllers.map(controller => {
let components = this.componentsForController(this.topComponents(), controller);
return { controller, components };
});
}
/**
* Builds a tree of components that have a specific controller
* as their target. If a component does not match the given
* controller, we ignore it and move on to its children.
*
* Format:
* [
* {
* value: |inspected component|,
* children: [...]
* },
* {
* value: |inspected component|
* children: [{
* value: |inspected component|
* children: [...]
* }]
* }
* ]
*
* @method componentsForController
* @param {Array} components Subtree of components
* @param {Controller} controller
* @return {Array} Array of inspected components
*/
componentsForController(components, controller) {
let arr = [];
components.forEach(component => {
let currentController = this.controllerForComponent(component);
if (!currentController) {
return;
}
let children = this.componentsForController(this.childComponents(component), controller);
if (currentController === controller) {
arr.push({ value: this.inspectComponent(component), children });
} else {
arr = arr.concat(children);
}
});
return arr;
}
/**
* Given a component, return its children.
*
* @method childComponents
* @param {Component} component The parent component
* @return {Array} Array of components (children)
*/
childComponents(component) {
return getChildViews(component);
}
/**
* Get the top level components.
*
* @method topComponents
* @return {Array} Array of components
*/
topComponents() {
return getRootViews(this.owner);
}
/**
* Assign each component tree to it matching outlet
* by comparing controllers.
*
* Return format:
* {
* value: |inspected root outlet|
* children: [
* {
* value: |inspected outlet or component|
* chidren: [...]
* },
* {
* value: |inspected outlet or component|
* chidren: [...]
* }
* ]
* }
*
* @method addComponentsToOutlets
* @param {Object} outletTree
* @param {Object} componentTrees
*/
addComponentsToOutlets(outletTree, componentTrees) {
let { value, controller, children } = outletTree;
children = children.map(child => this.addComponentsToOutlets(child, componentTrees));
let { components } = A(componentTrees).findBy('controller', controller) || { components: [] };
return { value, children: children.concat(components) };
}
/**
* @method controllersFromOutletTree
*
* @param {Controller} inspectedOutlet
* @return {Array} List of controllers
*/
controllersFromOutletTree({ controller, children }) {
return [controller].concat(...children.map(this.controllersFromOutletTree.bind(this)));
}
/**
* @method getRouter
* @return {Router}
*/
getRouter() {
return this.owner.lookup('router:main');
}
/**
* Returns the current top level view.
*
* @method getRoot
* @return {OutletView}
*/
getRoot() {
return this.getRouter().get('_toplevelView');
}
/**
* Returns the application (top) outlet.
*
* @return {Object} The application outlet state
*/
getApplicationOutlet() {
// Support multiple paths to outletState for various Ember versions
const outletState = this.getRoot().outletState || this.getRoot().state.ref.outletState;
return outletState.outlets.main;
}
/**
* The root's DOM element. The root is the only outlet view
* with a DOM element.
*
* @method elementForRoot
* @return {Element}
*/
elementForRoot() {
let renderer = this.owner.lookup('renderer:-dom');
return renderer._roots && renderer._roots[0] && renderer._roots[0].result && renderer._roots[0].result.firstNode();
}
/**
* Returns a component's template name.
*
* @method templateForComponent
* @param {Component} component
* @return {String} The template name
*/
templateForComponent(component) {
let template = component.get('layoutName');
if (!template) {
let layout = component.get('layout');
if (!layout) {
let componentName = component.get('_debugContainerKey');
if (componentName) {
let layoutName = componentName.replace(/component:/, 'template:components/');
layout = this.owner.lookup(layoutName);
}
}
template = this.nameFromLayout(layout);
}
return template;
}
/**
* Inspects and outlet state. Extracts the name, controller, template,
* and model.
*
* @method inspectOutlet
* @param {Object} outlet The outlet state
* @return {Object} The inspected outlet
*/
inspectOutlet(outlet) {
let name = this.nameForOutlet(outlet);
let template = this.templateForOutlet(outlet);
let controller = this.controllerForOutlet(outlet);
let value = {
controller: this.inspectController(controller),
template,
name,
isComponent: false,
// Outlets (except root) don't have elements
tagName: ''
};
let model = controller.get('model');
if (model) {
value.model = this.inspectModel(model);
}
return value;
}
/**
* Represents the controller as a short and long name + guid.
*
* @method inspectController
* @param {Controller} controller
* @return {Object} The inspected controller.
*/
inspectController(controller) {
return {
name: (0, _nameFunctions.shortControllerName)(controller),
completeName: (0, _nameFunctions.shortControllerName)(controller),
objectId: this.retainObject(controller)
};
}
/**
* Represent a component as a hash containing a template,
* name, objectId, class, render duration, tag, model.
*
* @method inspectComponent
* @param {Component} component
* @return {Object} The inspected component
*/
inspectComponent(component) {
let viewClass = (0, _nameFunctions.shortViewName)(component);
let completeViewClass = viewClass;
let tagName = component.get('tagName');
let objectId = this.retainObject(component);
let duration = this.durations[objectId];
let name = (0, _nameFunctions.shortViewName)(component);
let template = this.templateForComponent(component);
let value = {
template,
name,
objectId,
viewClass,
duration,
completeViewClass,
isComponent: true,
tagName: isNone(tagName) ? 'div' : tagName
};
let model = this.modelForComponent(component);
if (model) {
value.model = this.inspectModel(model);
}
return value;
}
/**
* Simply returns the component's model if it
* has one.
*
* @method modelForComponent
* @param {Component} component
* @return {Any} The model property
*/
modelForComponent(component) {
return component.get('model');
}
/**
* Represent a model as a short name, long name,
* guid, and type.
*
* @method inspectModel
* @param {Any} model
* @return {Object} The inspected model.
*/
inspectModel(model) {
if (EmberObject.detectInstance(model) || typeOf(model) === 'array') {
return {
name: (0, _nameFunctions.shortModelName)(model),
completeName: (0, _nameFunctions.modelName)(model),
objectId: this.retainObject(model),
type: 'type-ember-object'
};
}
return {
name: this.objectInspector.inspect(model),
type: `type-${typeOf(model)}`
};
}
/**
* Uses the module name that was set during compilation.
*
* @method nameFromLayout
* @param {Layout} layout
* @return {String} The layout's name
*/
nameFromLayout(layout) {
let moduleName = layout && get(layout, 'meta.moduleName');
if (moduleName) {
return moduleName.replace(/\.hbs$/, '');
}
}
/**
* Taekes an outlet state and extracts the controller from it.
*
* @method controllerForOutlet
* @param {Controller} outletState
* @return {Controller}
*/
controllerForOutlet(outletState) {
return outletState.render.controller;
}
/**
* The outlet's name.
*
* @method nameForOutlet
* @param {Object} outletState
* @return {String}
*/
nameForOutlet(outletState) {
return outletState.render.name;
}
/**
* The outlet's template name. Uses the module name attached during compilation.
*
* @method templateForOutlet
* @param {Object} outletState
* @return {String} The template name
*/
templateForOutlet(outletState) {
let template = outletState.render.template;
return this.nameFromLayout(template);
}
/**
* Returns a component's controller. The controller is either the component's
* target object, or the target object of one of its ancestors. That is why
* the method is recursive.
*
* @method controllerForComponent
* @param {Component} component
* @return {Controller} The target controller.
*/
controllerForComponent(component) {
let controller = component.get('_target') || component.get('_targetObject');
if (!controller) {
return null;
}
if (controller instanceof Controller) {
return controller;
} else {
return this.controllerForComponent(controller);
}
}
/**
* Renders a rectangle around a component's element. This happens
* when the user either hovers over the view tree components
* or clicks on the "inspect" magnifying glass and starts
* hovering over the components themselves.
*
* Pass `isPreview` if you want the highlight to be hidden
* when the mouse leaves the component. Set `isPreview` to false
* to render a [permanent] rectangle until the (x) button is clicked.
*
*
* @method highlightComponent
* @param {Element} element The element to highlight
* @param {Boolean} isPreview Whether it's a preview or not
*/
highlightComponent(component, isPreview = false) {
let rect = getViewBoundingClientRect(component);
let options = {
isPreview,
view: {
name: (0, _nameFunctions.shortViewName)(component),
object: component
}
};
let templateName = this.templateForComponent(component);
if (templateName) {
options.template = {
name: templateName
};
}
this.highlightRange(rect, options);
}
/**
* Renders a rectangle around the top level outlet's element. This happens
* when the user either hovers over the view tree root outlets
* or clicks on the "inspect" magnifying glass and starts
* hovering over the application template.
*
* Pass `isPreview` if you want the highlight to be hidden
* when the mouse leaves the root. Set `isPreview` to false
* to render a [permanent] rectangle until the (x) button is clicked.
*
* @method highlightRoot
* @param {Boolean} isPreview
*/
highlightRoot(isPreview = false) {
let applicationOutlet = this.getApplicationOutlet();
let element = this.elementForRoot();
if (!element) {
return;
}
let options = {
isPreview,
element,
template: {
name: this.templateForOutlet(applicationOutlet)
}
};
let controller = this.controllerForOutlet(applicationOutlet);
if (controller) {
options.controller = {
name: (0, _nameFunctions.shortControllerName)(controller),
object: controller
};
let model = controller.get('model');
if (model) {
let modelName = this.objectInspector.inspect(model);
options.model = {
name: modelName,
object: model
};
}
}
let rect = this.getBoundingClientRect(element);
this.highlightRange(rect, options);
}
/**
* Same as `ViewUtils.getBoundingClientRect` except this applies to
* HTML elements instead of components.
*
* @method getBoundingClientRect
* @param {Element} element
* @return {DOMRect
*/
getBoundingClientRect(element) {
let range = document.createRange();
range.setStartBefore(element);
range.setEndAfter(element);
return range.getBoundingClientRect();
}
/**
* Highlight an element only if it is a root.
*
* @method highlightIfRoot
* @param {String} elementId
* @param isPreview
*/
highlightIfRoot(elementId, isPreview = false) {
let element = document.getElementById(elementId);
if (this.isRootElement(element)) {
this.highlightRoot(isPreview);
}
}
/**
* Call this method when you have the id of an element you want
* to highlight but are unsure if that element represents a component