Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feat(wtf): add wtf support to (set/clear)Timeout/Interval/Immediate
Browse files Browse the repository at this point in the history
This rewrites the (set/clear)Timeout/Interval/Immediate method to render
information in WTF. It also makes the code more explicit, by removing
the need for bind method and instead creating explicit callbacks. The
resulting code is faster, and easier to read and reason about.
  • Loading branch information
mhevery committed Dec 16, 2015
1 parent c6cfdb4 commit 6659fd5
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 93 deletions.
1 change: 1 addition & 0 deletions karma-browserify.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = function (config) {
basePath: '',
files: [
'test/util.js',
'test/wtf_mock.js',
'test/commonjs.spec.js',
{pattern: 'test/assets/**/*.*', watched: true, served: true, included: false},
{pattern: 'lib/**/*.js', watched: true, served: false, included: false}
Expand Down
1 change: 1 addition & 0 deletions karma-microtasks.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = function (config) {
basePath: '',
files: [
'test/util.js',
'test/wtf_mock.js',
'test/setup-microtask.js',
'examples/js/*.js',
'test/**/*.spec.js',
Expand Down
1 change: 1 addition & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = function (config) {
basePath: '',
files: [
'test/util.js',
'test/wtf_mock.js',
'test/setup.js',
'example/js/*.js',
//'test/lib/brick.js',
Expand Down
31 changes: 29 additions & 2 deletions lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

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

var deprecated = {};

function deprecatedWarning(key, text) {
if (!deprecated.hasOwnProperty(key)) {
deprecated[key] = true;
console.warn("DEPRECATION WARNING: '" + key +
"' is no longer supported and will be removed in next major release. " + text);
}
}

function Zone(parentZone, data) {
var zone = (arguments.length) ? Object.create(parentZone) : this;

Expand Down Expand Up @@ -70,7 +80,9 @@ Zone.prototype = {
};
},

/// @deprecated
bindOnce: function (fn) {
deprecatedWarning('bindOnce', 'There is no replacement.');
var boundZone = this;
return this.bind(function () {
var result = fn.apply(this, arguments);
Expand Down Expand Up @@ -120,8 +132,23 @@ Zone.prototype = {
beforeTask: function () {},
onZoneCreated: function () {},
afterTask: function () {},
enqueueTask: function () {},
dequeueTask: function () {},

enqueueTask: function () {
deprecatedWarning('enqueueTask', 'Use addTask/addRepeatingTask/addMicroTask');
},
dequeueTask: function () {
deprecatedWarning('dequeueTask', 'Use removeTask/removeRepeatingTask/removeMicroTask');
},

addTask: function(taskFn) { this.enqueueTask(taskFn); },
removeTask: function(taskFn) { this.dequeueTask(taskFn); },

addRepeatingTask: function(taskFn) { this.enqueueTask(taskFn); },
removeRepeatingTask: function(taskFn) { this.dequeueTask(taskFn); },

addMicrotask: function(taskFn) { this.enqueueTask(taskFn); },
removeMicrotask: function(taskFn) { this.dequeueTask(taskFn); },

addEventListener: function () {
return this[keys.common.addEventListener].apply(this, arguments);
},
Expand Down
6 changes: 3 additions & 3 deletions lib/patch/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ var fileReaderPatch = require('./file-reader');

function apply() {
fnPatch.patchSetClearFunction(global, [
'timeout',
'interval',
'immediate'
'Timeout',
'Interval',
'Immediate'
]);

fnPatch.patchRequestAnimationFrame(global, [
Expand Down
133 changes: 76 additions & 57 deletions lib/patch/functions.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,98 @@
'use strict';

var utils = require('../utils');
var wtf = require('../wtf');
var Zone = require('../core').Zone;

function patchSetClearFunction(obj, fnNames) {
fnNames.map(function (name) {
return name[0].toUpperCase() + name.substr(1);
}).forEach(function (name) {
function patchSetClearFunction(window, fnNames) {
fnNames.forEach(function (name) {
var repeating = name == 'Interval';
var setName = 'set' + name;
var delegate = obj[setName];

if (delegate) {
var clearName = 'clear' + name;
var ids = {};
var clearName = 'clear' + name;
var setNative = window[setName];
var clearNative = window[clearName];
var ids = {};

if (setNative) {
var wtfSetEventFn = wtf.createEvent('Zone#' + setName + '(uint32 zone, uint32 id, uint32 delay)');
var wtfClearEventFn = wtf.createEvent('Zone#' + clearName + '(uint32 zone, uint32 id)');
var wtfCallbackFn = wtf.createScope('Zone#cb:' + name + '(uint32 zone, uint32 id, uint32 delay)');

// Forward all calls from the window through the zone.
window[setName] = function () {
return global.zone[setName].apply(global.zone, arguments);
};
window[clearName] = function () {
return global.zone[clearName].apply(global.zone, arguments);
};

var bindArgs = setName === 'setInterval' ? utils.bindArguments : utils.bindArgumentsOnce;

global.zone[setName] = function (fn) {
var id, fnRef = fn;
arguments[0] = function () {
delete ids[id];
return fnRef.apply(this, arguments);
// Set up zone processing for the set function.
Zone.prototype[setName] = function (fn, delay) {
// We need to save `fn` in var different then argument. This is because
// in IE9 `argument[0]` and `fn` have same identity, and assigning to
// `argument[0]` changes `fn`.
var callbackFn = fn;
if (typeof callbackFn !== 'function') {
// force the error by calling the method with wrong args
setNative.apply(window, arguments);
}
var zone = this;
var setId = null;
// wrap the callback function into the zone.
arguments[0] = function() {
var callbackZone = zone.isRootZone() || isRaf ? zone : zone.fork();
var callbackThis = this;
var callbackArgs = arguments;
return wtf.leaveScope(
wtfCallbackFn(callbackZone.$id, setId, delay),
callbackZone.run(function() {
if (!repeating) {
delete ids[setId];
callbackZone.removeTask(callbackFn);
}
return callbackFn.apply(callbackThis, callbackArgs);
})
);
};
var args = bindArgs(arguments);
id = delegate.apply(obj, args);
ids[id] = true;
return id;
if (repeating) {
zone.addRepeatingTask(callbackFn);
} else {
zone.addTask(callbackFn);
}
setId = setNative.apply(window, arguments);
ids[setId] = callbackFn;
wtfSetEventFn(zone.$id, setId, delay);
return setId;
};

obj[setName] = function () {
return global.zone[setName].apply(this, arguments);
Zone.prototype[setName + 'Unpatched'] = function() {
return setNative.apply(window, arguments);
};

var clearDelegate = obj[clearName];

global.zone[clearName] = function (id) {
if (ids[id]) {
// Set up zone processing for the clear function.
Zone.prototype[clearName] = function (id) {
var scope = wtfClearEventFn(this.$id, id);
if (ids.hasOwnProperty(id)) {
var callbackFn = ids[id];
delete ids[id];
global.zone.dequeueTask();
if (repeating) {
this.removeRepeatingTask(callbackFn);
} else {
this.removeTask(callbackFn);
}
}
return clearDelegate.apply(this, arguments);
return clearNative.apply(window, arguments);
};

global.zone[setName + 'Unpatched'] = function() {
return delegate.apply(obj, arguments);
Zone.prototype[clearName + 'Unpatched'] = function() {
return clearNative.apply(window, arguments);
};

global.zone[clearName + 'Unpatched'] = function() {
return clearDelegate.apply(obj, arguments);
};

obj[clearName] = function () {
return global.zone[clearName].apply(this, arguments);
};
}
}
fnNames.forEach(function(args) {
patchMacroTaskMethod.apply(null, args);
});
};

Expand Down Expand Up @@ -83,26 +123,6 @@ function patchRequestAnimationFrame(obj, fnNames) {
});
};

function patchSetFunction(obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];

if (delegate) {
global.zone[name] = function (fn) {
arguments[0] = function () {
return fn.apply(this, arguments);
};
var args = utils.bindArgumentsOnce(arguments);
return delegate.apply(obj, args);
};

obj[name] = function () {
return zone[name].apply(this, arguments);
};
}
});
};

function patchFunction(obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];
Expand All @@ -119,7 +139,6 @@ function patchFunction(obj, fnNames) {

module.exports = {
patchSetClearFunction: patchSetClearFunction,
patchSetFunction: patchSetFunction,
patchRequestAnimationFrame: patchRequestAnimationFrame,
patchFunction: patchFunction
};
10 changes: 0 additions & 10 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ function bindArguments(args) {
return args;
};

function bindArgumentsOnce(args) {
for (var i = args.length - 1; i >= 0; i--) {
if (typeof args[i] === 'function') {
args[i] = global.zone.bindOnce(args[i]);
}
}
return args;
};

function patchPrototype(obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];
Expand Down Expand Up @@ -199,7 +190,6 @@ function patchClass(className) {

module.exports = {
bindArguments: bindArguments,
bindArgumentsOnce: bindArgumentsOnce,
patchPrototype: patchPrototype,
patchProperty: patchProperty,
patchProperties: patchProperties,
Expand Down
51 changes: 51 additions & 0 deletions lib/wtf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

// Detect and setup WTF.
var wtfTrace = null;
var wtfEvents = null;
var wtfEnabled = (function () {
var wtf = global['wtf'];
if (wtf) {
wtfTrace = wtf['trace'];
if (wtfTrace) {
wtfEvents = wtfTrace['events'];
return true;
}
}
return false;
})();

function noop() {
}

if (wtfEnabled) {
module.exports = {
enabled: true,
createScope: function (signature, flags) {
return wtfEvents.createScope(signature, flags);
},
createEvent: function (signature, flags) {
return wtfEvents.createInstance(signature, flags);
},
leaveScope: function (scope, returnValue) {
wtfTrace.leaveScope(scope, returnValue);
return returnValue;
},
beginTimeRange:function (rangeType, action) {
return wtfTrace.beginTimeRange(rangeType, action);
},
endTimeRange: function (range) {
wtfTrace.endTimeRange(range);
}
};
} else {
module.exports = {
enabled: false,
createScope: function (s, f) { return noop; },
createEvent: function (s, f) { return noop; },
leaveScope: function (s, v) { return v; },
beginTimeRange: function (t, a) { return null; },
endTimeRange: function (r) { }
};
}

31 changes: 27 additions & 4 deletions test/patch/setInterval.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,39 @@
describe('setInterval', function () {

it('should work with setInterval', function (done) {
var testZone = zone.fork();
var testZone = zone.fork({
addTask: function(fn) { wtfMock.log.push('addTask ' + fn.id ); },
removeTask: function(fn) { wtfMock.log.push('removeTask ' + fn.id); },
addRepeatingTask: function(fn) { wtfMock.log.push('addRepeatingTask ' + fn.id); },
removeRepeatingTask: function(fn) { wtfMock.log.push('removeRepeatingTask ' + fn.id); },
});

var zId;
var setIntervalId = '?';
testZone.run(function() {
var cancelId = setInterval(function() {
zId = zone.$id;
var intervalFn = function () {
var zCallbackId = zone.$id;
// creates implied zone in all callbacks.
expect(zone).toBeDirectChildOf(testZone);

clearInterval(cancelId);
done();
}, 10);
zone.setTimeoutUnpatched(function() {
expect(wtfMock.log).toEqual([
'addRepeatingTask abc',
'# Zone#setInterval(' + zId + ', ' + cancelId + ', 10)',
'> Zone#cb:Interval(' + zCallbackId + ', ' + cancelId + ', 10)',
'# Zone#clearInterval(' + zCallbackId + ', ' + cancelId + ')',
'removeRepeatingTask abc',
'< Zone#cb:Interval'
]);
done();
});
};
intervalFn.id = 'abc';
var cancelId = setInterval(intervalFn, 10);
expect(wtfMock.log[0]).toEqual('addRepeatingTask abc');
expect(wtfMock.log[1]).toEqual('# Zone#setInterval(' + zId + ', ' + cancelId + ', 10)');
});
});

Expand Down
Loading

0 comments on commit 6659fd5

Please sign in to comment.