Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Catch errors triggered by the inspector. #343

Merged
merged 1 commit into from
May 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Brocfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ emberDebug = concatFiles(emberDebug, {
});

emberDebug = wrapFiles(emberDebug, {
wrapper: ["(function(adapter) {\n", "\n}('" + (dist || 'basic') + "'));"]
wrapper: ["(function(adapter, env) {\n", "\n}('" + (dist || 'basic') + "', '" + env + "'));"]
});

var tree = app.toTree();
Expand Down
54 changes: 51 additions & 3 deletions ember_debug/adapters/basic.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* globals requireModule */
var Ember = window.Ember;
var computed = Ember.computed;
var $ = Ember.$;
Expand All @@ -12,12 +13,32 @@ export default Ember.Object.extend({
}, 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: function() {
console.debug.apply(console, arguments);
return console.debug.apply(console, arguments);
},

log: function() {
console.log.apply(console, arguments);
return console.log.apply(console, arguments);
},

/**
* A wrapper for `console.warn`.
*
* @method warn
*/
warn: function() {
return console.warn.apply(console, arguments);
},

/**
Expand Down Expand Up @@ -58,6 +79,34 @@ export default Ember.Object.extend({
});
},

/**
* 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: function(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
Expand Down Expand Up @@ -110,5 +159,4 @@ export default Ember.Object.extend({
messages.clear();
this._isReady = true;
}

});
49 changes: 29 additions & 20 deletions ember_debug/models/profile-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,51 @@ var ProfileManager = function() {

ProfileManager.prototype = {
began: function(timestamp, payload, now) {
this.current = new ProfileNode(timestamp, payload, this.current, now);
return this.current;
return this.wrapForErrors(this, function() {
this.current = new ProfileNode(timestamp, payload, this.current, now);
return this.current;
});
},

ended: function(timestamp, payload, profileNode) {
if (payload.exception) { throw payload.exception; }
return this.wrapForErrors(this, function() {
this.current = profileNode.parent;
profileNode.finish(timestamp);

this.current = profileNode.parent;
profileNode.finish(timestamp);
// Are we done profiling an entire tree?
if (!this.current) {
this.currentSet.push(profileNode);
// If so, schedule an update of the profile list
scheduleOnce('afterRender', this, this._profilesFinished);
}
});
},

// Are we done profiling an entire tree?
if (!this.current) {
this.currentSet.push(profileNode);
// If so, schedule an update of the profile list
scheduleOnce('afterRender', this, this._profilesFinished);
}
wrapForErrors: function(context, callback) {
return callback.call(context);
},

clearProfiles: function() {
this.profiles.length = 0;
},

_profilesFinished: function() {
var firstNode = this.currentSet[0],
return this.wrapForErrors(this, function() {
var firstNode = this.currentSet[0],
parentNode = new ProfileNode(firstNode.start, { template: 'View Rendering' });

parentNode.time = 0;
this.currentSet.forEach(function(n) {
parentNode.time += n.time;
parentNode.children.push(n);
});
parentNode.calcDuration();
parentNode.time = 0;
this.currentSet.forEach(function(n) {
parentNode.time += n.time;
parentNode.children.push(n);
});
parentNode.calcDuration();

this.profiles.push(parentNode);
this._triggerProfilesAdded([parentNode]);
this.currentSet = [];
this.profiles.push(parentNode);
this._triggerProfilesAdded([parentNode]);
this.currentSet = [];
});
},

_profilesAddedCallbacks: undefined, // set to array on init
Expand Down
38 changes: 37 additions & 1 deletion ember_debug/port.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var Ember = window.Ember;
var oneWay = Ember.computed.oneWay;
var guidFor = Ember.guidFor;
var run = Ember.run;

export default Ember.Object.extend(Ember.Evented, {
adapter: oneWay('namespace.adapter').readOnly(),
Expand All @@ -15,14 +16,49 @@ export default Ember.Object.extend(Ember.Evented, {
var self = this;
this.get('adapter').onMessageReceived(function(message) {
if (self.get('uniqueId') === message.applicationId || !message.applicationId) {
self.trigger(message.type, message);
self.messageReceived(message.type, message);
}
});
},

messageReceived: function(name, message) {
var self = this;
this.wrap(function() {
self.trigger(name, message);
});
},

send: function(messageType, options) {
options.type = messageType;
options.from = 'inspectedWindow';
options.applicationId = this.get('uniqueId');
this.get('adapter').send(options);
},

/**
* Wrap all code triggered from outside of
* EmberDebug with this method.
*
* `wrap` is called by default
* on all callbacks triggered by `port`,
* so no need to call it in this case.
*
* - Wraps a callback in `Ember.run`.
* - Catches all errors during production
* and displays them in a user friendly manner.
*
* @method wrap
* @param {Function} fn
* @return {Mixed} The return value of the passed function
*/
wrap: function(fn) {
return run(this, function() {
try {
return fn();
} catch (error) {
this.get('adapter').handleError(error);
}
});
}
});

9 changes: 9 additions & 0 deletions ember_debug/render-debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,21 @@ export default Ember.Object.extend(PortMixin, {
profileManager: profileManager,

init: function() {
var self = this;
this._super();
this.profileManager.wrapForErrors = function(context, callback) {
return self.get('port').wrap(function() {
return callback.call(context);
});
};
this._subscribeForViewTrees();
},

willDestroy: function() {
this._super();
this.profileManager.wrapForErrors = function(context, callback) {
return callback.call(context);
};
this.profileManager.offProfilesAdded(this, this.sendAdded);
this.profileManager.offProfilesAdded(this, this._updateViewTree);
},
Expand Down
13 changes: 12 additions & 1 deletion ember_debug/vendor/startup-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
Also responsible for sending the first tree.
**/

/* globals Ember, adapter, requireModule */
/* globals Ember, adapter, env, requireModule */

var currentAdapter = 'basic';
if (typeof adapter !== 'undefined') {
currentAdapter = adapter;
}
var currentEnv = 'production';
if (typeof env !== 'undefined') {
currentEnv = env;
}

(function(adapter) {
onEmberReady(function() {
Expand All @@ -24,6 +28,13 @@ if (typeof adapter !== 'undefined') {
}
// prevent from injecting twice
if (!Ember.Debug) {
define('ember-debug/config', function() {
return {
default: {
environment: currentEnv
}
};
});
window.EmberInspector = Ember.Debug = requireModule('ember-debug/main')['default'];
Ember.Debug.Adapter = requireModule('ember-debug/adapters/' + adapter)['default'];

Expand Down
28 changes: 27 additions & 1 deletion tests/ember_debug/ember_debug_test.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
var name;
/* jshint ignore:start */
/* eslint no-empty:0 */
import Ember from "ember";
import { module, test } from 'qunit';

var EmberDebug;
var name;
var name, port, adapter;
/* jshint ignore:start */
var run = Ember.run;
var App;
var EmberInspector;

function setupApp() {
App = Ember.Application.create();
App.setupForTesting();
App.injectTestHelpers();

}

module("Ember Debug", {
Expand All @@ -31,6 +35,8 @@ module("Ember Debug", {
run(EmberDebug, 'start');
EmberDebug.start();
EmberInspector = EmberDebug;
port = EmberDebug.port;
adapter = EmberDebug.get('port.adapter');
},
afterEach() {
name = null;
Expand Down Expand Up @@ -60,3 +66,23 @@ test("EmberInspector#inspect sends inspectable objects", function(assert) {
cantSend("a", assert);
cantSend(null, assert);
});

test("Errors are caught and handled by EmberDebug", async function t(assert) {
assert.expect(1);
const error = new Error('test error');
port.on('test:errors', () => {
throw error;
});

const handleError = adapter.handleError;
adapter.reopen({
handleError(e) {
assert.equal(e, error, 'Error handled');
}
});

port.messageReceived('test:errors', {});

await wait();
adapter.reopen({ handleError });
});