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

Fix crash introduced in v0.10.8 and slightly improve loader #311

Closed
wants to merge 3 commits into from
Closed
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
22 changes: 13 additions & 9 deletions background.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function brokerMessage(message, sender) {
transformMessage(tabId, message);
bufferData(tabId, message);
}

if (devToolsPort) {
devToolsPort.postMessage(message);
}
Expand Down Expand Up @@ -47,8 +48,8 @@ function transformMessage(tabId, message) {
}

if (message.event === 'model:change') {
message.data.value = (typeof message.data.value === 'undefined') ?
undefined : JSON.parse(message.data.value)
message.data.value = (message.data.value === undefined) ?
undefined : JSON.parse(message.data.value);
}
}

Expand All @@ -71,13 +72,18 @@ function bufferData(tabId, message) {
}

if (message.event === 'scope:new') {
tabData.scopes[message.data.child] = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I note that this change no longer modifies the tabData.scope object. Is that on purpose?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does. I just stored some properties as local var (childScopeData = tabData.scopes[childId]).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At no point do you write to tabData.scopes[message.data.child] any more

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the childScopeData never gets assigned onto the tabData.scopes object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OMG, you are right 😱

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this was used to "hydrate" the Scopes view when the DevTools would open, at which point the DevTools app would take over maintaining the scopes info. With my incorrect change, the scopes would not be hydrated when you opened DevTools, so you needed to refresh the app with the DevTools open.

Thx for watching out for my blunders 😃
It really sucks that there are no substancial tests. Hoping to fix it some time.

parent: message.data.parent,
var childId = message.data.child;
var parentId = message.data.parent;
var parentScopeData = tabData.scopes[parentId];

tabData.scopes[childId] = {
parent: parentId,
children: [],
models: {}
};
if (tabData.scopes[message.data.parent]) {
tabData.scopes[message.data.parent].children.push(message.data.child);

if (parentScopeData) {
parentScopeData.children.push(childId);
}
} else if (message.data.id && (scope = tabData.scopes[message.data.id])) {
if (message.event === 'scope:destroy') {
Expand All @@ -101,8 +107,7 @@ function bufferData(tabId, message) {
// context script –> background
chrome.runtime.onMessage.addListener(brokerMessage);

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is it with adding spaces after function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It falls under the random changes category 😃
Both variations are used throughout the project - it drives me crazy. I was trying to blend in with the rest of the file 😁

devToolsPort.onMessage.addListener(registerInspectedTabId);

function registerInspectedTabId(inspectedTabId) {
Expand All @@ -122,7 +127,6 @@ chrome.runtime.onConnect.addListener(function(devToolsPort) {

//devToolsPort.onMessage.removeListener(registerInspectedTabId);
}

});

chrome.tabs.onRemoved.addListener(function (tabId) {
Expand Down
73 changes: 31 additions & 42 deletions devtoolsBackground.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,47 @@
var panels = chrome && chrome.devtools && chrome.devtools.panels;
var elementsPanel = panels && panels.elements;

if (elementsPanel) {
elementsPanel.createSidebarPane('$scope', function onSidebarCreated(sidebar) {
elementsPanel.onSelectionChanged.addListener(function updateElementProperties() {
sidebar.setExpression('(' + getPanelContents.toString() + ')()');
});

// AngularJS panel
panels.create('AngularJS', 'img/angular.png', 'panel/app.html');
});
}

// The function below is executed in the context of the inspected page.
function getPanelContents() {
var angular = window.angular;
var panelContents = {};

var getPanelContents = function () {
if (window.angular && $0) {
//TODO: can we move this scope export into updateElementProperties
if (angular && $0) {
var scope = getScope($0);

// Export $scope to the console
window.$scope = scope;
return (function (scope) {
var panelContents = {
__private__: {}
};

for (prop in scope) {
if (scope.hasOwnProperty(prop)) {
if (prop.substr(0, 2) === '$$') {
panelContents.__private__[prop] = scope[prop];
} else {
panelContents[prop] = scope[prop];
}
}
}
return panelContents;
}(scope));
} else {
return {};

// Get sidebar contents
panelContents.__private__ = {};
Object.keys(scope).forEach(function (prop) {
var dest = (prop.substr(0, 2) === '$$') ? panelContents.__private__ : panelContents;
dest[prop] = scope[prop];
});
}

return panelContents;

// Helpers
function getScope(node) {
var scope = window.angular.element(node).scope();
var scope = angular.element(node).scope();
if (!scope) {
// Might be a child of a DocumentFragment...
while (node && node.nodeType === 1) node = node.parentNode;
if (node && node.nodeType === 11) node = (node.parentNode || node.host);
return getScope(node);
while (node && node.nodeType === Node.ELEMENT_NODE) node = node.parentNode;
if (node && node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) node = node.parentNode || node.host;
return node && getScope(node);
}
return scope;
}
};

panels && panels.elements.createSidebarPane(
"$scope",
function (sidebar) {
panels.elements.onSelectionChanged.addListener(function updateElementProperties() {
sidebar.setExpression("(" + getPanelContents.toString() + ")()");
});

// Angular panel
var angularPanel = panels.create(
"AngularJS",
"img/angular.png",
"panel/app.html"
);
});

}
179 changes: 146 additions & 33 deletions dist/hint.js
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,152 @@
*
* This gets loaded into the context of the app you are inspecting
*/
require('./loader.js');
require('./loader');
require('angular-hint');

angular.hint.onAny(function (data, severity) {
angular.hint.onAny(function (event, data, severity) {
// EventEmitter2 usually omits the event type for the argument list (assigning it to `this.event`
// instead), but under certain circumstances it may include it.
if (this.event !== event) {
severity = data;
data = event;
event = this.event;
}

window.postMessage({
__fromBatarang: true,
module: this.event.split(':')[0],
event: this.event,
module: event.split(':')[0],
event: event,
data: data,
severity: severity
}, '*');
});

},{"./loader.js":2,"angular-hint":3}],2:[function(require,module,exports){
},{"./loader":2,"angular-hint":3}],2:[function(require,module,exports){
// BATARANG
// Loader file based on `angular-loader.js` v1.6.5-local+sha.59dbff0c7
// (https://github.com/angular/angular.js/pull/15881).
// Modified regions should be marked with `// BATARANG` comments.
/**
* @license AngularJS v1.6.4-build.5322+sha.d96e58f
* @license AngularJS v1.6.5-local+sha.59dbff0c7
* (c) 2010-2017 Google, Inc. http://angularjs.org
* License: MIT
*/

(function() {'use strict';
function isFunction(value) {return typeof value === 'function';}
function isDefined(value) {return typeof value !== 'undefined';}
function isNumber(value) {return typeof value === 'number';}
function isObject(value) {return value !== null && typeof value === 'object';}
function isScope(obj) {return obj && obj.$evalAsync && obj.$watch;}
function isWindow(obj) {return obj && obj.window === obj;}
function sliceArgs(args, startIndex) {return Array.prototype.slice.call(args, startIndex || 0);}
function toJsonReplacer(key, value) {
var val = value;

if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
val = undefined;
} else if (isWindow(value)) {
val = '$WINDOW';
} else if (value && window.document === value) {
val = '$DOCUMENT';
} else if (isScope(value)) {
val = '$SCOPE';
}

return val;
}

/* exported toDebugString */

// This file is also included in `angular-loader`, so `copy()` might not always be available in the
// closure. In such cases, it is lazily retrieved as `angular.copy()` when needed.
var copyFn;

function serializeObject(obj, maxDepth) {
var seen = [];

// There is no direct way to stringify object until reaching a specific depth
// and a very deep object can cause a performance issue, so we copy the object
// based on this specific depth and then stringify it.
if (isValidObjectMaxDepth(maxDepth)) {
if (!copyFn) {
copyFn = copy || angular.copy;
}
obj = copyFn(obj, null, maxDepth);
}
return JSON.stringify(obj, function(key, val) {
val = toJsonReplacer(key, val);
if (isObject(val)) {

if (seen.indexOf(val) >= 0) return '...';

seen.push(val);
}
return val;
});
}

function toDebugString(obj, maxDepth) {
if (typeof obj === 'function') {
return obj.toString().replace(/ \{[\s\S]*$/, '');
} else if (isUndefined(obj)) {
return 'undefined';
} else if (typeof obj !== 'string') {
return serializeObject(obj, maxDepth);
}
return obj;
}

/* exported
minErrConfig,
errorHandlingConfig,
isValidObjectMaxDepth
*/

var minErrConfig = {
objectMaxDepth: 5
};

/**
* @ngdoc function
* @name angular.errorHandlingConfig
* @module ng
* @kind function
*
* @description
* Configure several aspects of error handling in AngularJS if used as a setter or return the
* current configuration if used as a getter. The following options are supported:
*
* - **objectMaxDepth**: The maximum depth to which objects are traversed when stringified for error messages.
*
* Omitted or undefined options will leave the corresponding configuration values unchanged.
*
* @param {Object=} config - The configuration object. May only contain the options that need to be
* updated. Supported keys:
*
* * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a
* non-positive or non-numeric value, removes the max depth limit.
* Default: 5
*/
function errorHandlingConfig(config) {
if (isObject(config)) {
if (isDefined(config.objectMaxDepth)) {
minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN;
}
} else {
return minErrConfig;
}
}

function isFunction(value) { return typeof value === 'function'; }
function isDefined(value) { return typeof value !== 'undefined'; }
function isObject(value) { return value !== null && typeof value === 'object'; }
/**
* @private
* @param {Number} maxDepth
* @return {boolean}
*/
function isValidObjectMaxDepth(maxDepth) {
return isNumber(maxDepth) && maxDepth > 0;
}

/**
* @description
Expand Down Expand Up @@ -61,25 +181,15 @@ function isObject(value) { return value !== null && typeof value === 'object'; }
* @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
*/

function toDebugString(obj) {
if (typeof obj === 'function') {
return obj.toString().replace(/ ?\{[\s\S]*$/, '');
} else if (typeof obj === 'undefined') {
return 'undefined';
} else if (typeof obj !== 'string') {
return JSON.stringify(obj);
}
return obj;
}

function minErr(module, ErrorConstructor) {
ErrorConstructor = ErrorConstructor || Error;
return function() {
var code = arguments[0],
template = arguments[1],
version = (angular.version && angular.version.full) || 'snapshot',
message = '[' + (module ? module + ':' : '') + code + '] ',
templateArgs = Array.prototype.slice.call(arguments, 2).map(toDebugString),
templateArgs = sliceArgs(arguments, 2).map(function(arg) {
return toDebugString(arg, minErrConfig.objectMaxDepth);
}),
paramPrefix, i;

message += template.replace(/\{\d+\}/g, function(match) {
Expand All @@ -88,9 +198,13 @@ function minErr(module, ErrorConstructor) {
if (index < templateArgs.length) {
return templateArgs[index];
}

return match;
});

// BATARANG
// Use the app's version in error URLs instead of the version this file was based on.
var version = (angular.version && angular.version.full) || 'snapshot';
message += '\nhttp://errors.angularjs.org/' + version + '/' +
(module ? module + '/' : '') + code;

Expand Down Expand Up @@ -215,9 +329,11 @@ function setupModuleLoader(window) {
/** @type {angular.Module} */
var moduleInstance = {
// Private state
// ANGULAR HINT ALTERATION
// See the definition of `_invokeQueue` below for more info.
_configBlocks: [],
// BATARANG
// `_invokeQueue` needs to be handled in a special way in order to support both v1.2 and
// v1.3+ apps. See its definition below for more details.
//_invokeQueue: invokeQueue,
_configBlocks: configBlocks,
_runBlocks: runBlocks,

/**
Expand Down Expand Up @@ -469,16 +585,13 @@ function setupModuleLoader(window) {
}
};

/**
* ANGULAR HINT ALTERATION
* To make this loader compatible with apps that are running both Angular 1.2 and 1.3+, the
* loader must handle 1.3+ applications that expect to initialize their config blocks after
* all providers are registered. Hence, an empty `_configBlocks` array is exposed on
* `moduleInstance` and the actual `configBlocks` are added to the end of the `invokeQueue`.
*/
// BATARANG
// To make this loader compatible with both AngularJS v1.2 and v1.3+ apps, it must handle
// v1.2 apps that do not know about `_configBlocks` and expect everything to be on the
// `_invokeQueue`.
Object.defineProperty(moduleInstance, '_invokeQueue', {
get: function() {
return invokeQueue.concat(configBlocks);
return (angular.version.minor <= 2) ? invokeQueue.concat(configBlocks) : invokeQueue;
}
});

Expand Down
Loading