├── .gitattributes
├── .gitignore
├── .npmignore
├── README.md
├── bower.json
├── index.html
├── jquery-observe.js
├── package.json
└── test
├── benchmark.js
├── helper.js
├── integration.html
├── integration.js
└── performance.html
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 | [Dd]ebug/
46 | [Rr]elease/
47 | *_i.c
48 | *_p.c
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.vspscc
63 | .builds
64 | *.dotCover
65 |
66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this
67 | #packages/
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 |
76 | # Visual Studio profiler
77 | *.psess
78 | *.vsp
79 |
80 | # ReSharper is a .NET coding add-in
81 | _ReSharper*
82 |
83 | # Installshield output folder
84 | [Ee]xpress
85 |
86 | # DocProject is a documentation generator add-in
87 | DocProject/buildhelp/
88 | DocProject/Help/*.HxT
89 | DocProject/Help/*.HxC
90 | DocProject/Help/*.hhc
91 | DocProject/Help/*.hhk
92 | DocProject/Help/*.hhp
93 | DocProject/Help/Html2
94 | DocProject/Help/html
95 |
96 | # Click-Once directory
97 | publish
98 |
99 | # Others
100 | [Bb]in
101 | [Oo]bj
102 | sql
103 | TestResults
104 | *.Cache
105 | ClientBin
106 | stylecop.*
107 | ~$*
108 | *.dbmdl
109 | Generated_Code #added for RIA/Silverlight projects
110 |
111 | # Backup & report files from converting an old project file to a newer
112 | # Visual Studio version. Backup files are not needed, because we have git ;-)
113 | _UpgradeReport_Files/
114 | Backup*/
115 | UpgradeLog*.XML
116 |
117 |
118 |
119 | ############
120 | ## Windows
121 | ############
122 |
123 | # Windows image file caches
124 | Thumbs.db
125 |
126 | # Folder config file
127 | Desktop.ini
128 |
129 |
130 | #############
131 | ## Python
132 | #############
133 |
134 | *.py[co]
135 |
136 | # Packages
137 | *.egg
138 | *.egg-info
139 | dist
140 | build
141 | eggs
142 | parts
143 | bin
144 | var
145 | sdist
146 | develop-eggs
147 | .installed.cfg
148 |
149 | # Installer logs
150 | pip-log.txt
151 |
152 | # Unit test / coverage reports
153 | .coverage
154 | .tox
155 |
156 | #Translations
157 | *.mo
158 |
159 | #Mr Developer
160 | .mr.developer.cfg
161 |
162 | # Mac crap
163 | .DS_Store
164 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 | bower.json
3 | index.html
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jQuery Observe
2 |
3 | A jQuery plugin which simplifies the use of the new [DOM Mutation Observer][w3_mo] interface introduced in newer browsers. `jquery-observe.js` contains the compiled and minified version of the plugin.
4 |
5 | Available through `bower`
6 |
7 | bower install jquery-observe
8 |
9 | And `npm`
10 |
11 | npm install @kapetan/jquery-observer
12 |
13 | # Compatibility
14 |
15 | At this point the Mutation Observer API is only available in newer versions of Google Chrome (>= 18) and Mozilla Firefox (>= 14). Can be useful when developing extensions (add-ons) that use content scripts, on a page that you don't control.
16 |
17 | There is a fallback for browsers who don't support the Mutation Observer API (mainly IE9 and Opera), using the deprecated [DOM Mutation Events interface][w3_me]. These don't act completely as Mutation Observers, and can cause some different behaviour between browsers.
18 | For instance an event is fired for every descendant of an inserted node, where only one record is dispatched when using Mutation Observers. Furthermore because mutation events don't have the same information associated with them as Mutation Observer records, some contextual selectors may not match removed nodes. Some of those are `:first`, `:first-child`, `:eq`, `:last`, `:last-child`, `:even` and `:odd` (not an exhaustive list).
19 |
20 | # Usage
21 |
22 | The observe interface `$.fn.observe()` is somewhat similar to the jQuery event api using the `$.fn.on()` method.
23 |
24 | ```javascript
25 | $('#content')
26 | .observe('attributes', function(record) {
27 | // Observe attribute changes on #content
28 | })
29 | .observe('childlist subtree', function(record) {
30 | // Observe changes in the subtree
31 | })
32 | .observe({ attributes: true, attributeFilter: ['class'] }, function(record) {
33 | // Observe changes in attribute class on #content
34 | });
35 | ```
36 | The callback function gets passed the [MutationRecord][w3_mr] instance matching the query.
37 |
38 | The first argument can either be a string or an object containing the options which are passed to `MutaionObserver.observe()` method. See the [w3c documentation][w3_mo] for Mutation Observer for more information on the available options.
39 |
40 | All the above observers are collapsed into a single Mutation Observer object using #content as target.
41 |
42 | The real power comes when using a selector to filter the elements.
43 |
44 | ```javascript
45 | $('#content')
46 | .observe('childlist', 'ul li:first', function(record) {
47 | // Observe if elements matching '#content ul li:first' have been added or removed
48 | })
49 | .observe('attributes', '.section p:visible', function(record) {
50 | // Observe if elements matching '#content .section p:visible' have been added or removed
51 | })
52 | ```
53 |
54 | In the above callback functions `this` refers to the available matched DOM element. In the case where no selector is given `this` always refers to the element which `.observe()` was called on. When a selector is present `this` references different elements.
55 |
56 | * Records of type *attributes*: `this` refers to the node which had an attribute changed.
57 |
58 | * Records of type *characterData*: `this` refers to the parent element of the text node which had its content modified. The modified text node can be retrieved trough `record.target`.
59 |
60 | * Records of type *childList* and the selector matches an added node: `this` refers to the added node.
61 |
62 | * Records of type *childList* and the selector matches a removed node: `this` refers to the **parent** of the removed node (since the removed node is no longer available in the DOM).
63 |
64 | This also means that the callback is called for every matched element (similar to `$.fn.on()`).
65 |
66 | Using the above defined observers with the following HTML:
67 |
68 | ```html
69 |
70 |
71 |
72 |
73 |
Hello
74 |
75 | ```
76 |
77 | * Running `$('#content ul').append('') // added as last element` will **not** trigger the first observer neither will `$('#content ul li:first').append('')`, since the observe selector only matches nodes that either have been added or removed.
78 |
79 | * Running `$('#content ul').prepend('')` will trigger the first observer, since a *li* element was inserted as the first child. And `this` will reference to the newly inserted node.
80 |
81 | * Running `$('#content ul li:first').remove()` also triggers the first observer. In this case `this` will reference to the parent of the removed element (the *ul* element).
82 |
83 | * Running `$('#content ul li:first span').remove()` will **not** trigger the first observer.
84 |
85 | * Running `$('#content span p').addClass('myClass')` triggers the second observer.
86 |
87 | Note that the changes to the DOM don't have to be performed using jQuery. The last example can also be run using plain javascript `document.getElementsByClassName('hello')[0].className += ' myClass'`.
88 |
89 | ### Extended options
90 |
91 | There are two custom Mutation Observer options which can be used together with the other options. These are *added* and *removed*. Which only trigger an observer if either a node has been added or removed.
92 |
93 | ```javascript
94 | $('#content')
95 | .observe('added', 'li:first', function(record) {
96 | // Only called if a node matching '#content li:first' has been added
97 | })
98 | .observe('removed', 'li:first', function(record) {
99 | // Only called if a node matching '#content li:first' has been removed
100 | });
101 | ```
102 |
103 | Using the options `'added removed'` is equivalent to `'childlist'`.
104 |
105 | ### Removing observers
106 |
107 | Use the `$.fn.disconnect()` method to remove an observer. The arguments must match the arguments given to the `$.fn.observe()` method. Or call the disconnect method without arguments to remove all observers. The underlying Mutation Observer is disabled when there are no observers listening for changes.
108 |
109 | # Issues
110 |
111 | There are some problems getting the characterData option to work in Chrome (may be because of bug [#134322][chrome_bug]).
112 |
113 | # License
114 |
115 | **This software is licensed under "MIT"**
116 |
117 | > Copyright (c) 2012 Mirza Kapetanovic
118 | >
119 | > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
120 | >
121 | > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
122 | >
123 | > THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
124 |
125 | [w3_mo]: http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#mutation-observers "Mutation Observer"
126 | [w3_mr]: http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#mutationrecord "Mutaion Record"
127 | [w3_me]: http://www.w3.org/TR/DOM-Level-3-Events/#events-mutationevents "Mutaion Events"
128 | [chrome_bug]: http://code.google.com/p/chromium/issues/detail?id=134322 "Chrome bug #134322"
129 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery-observe",
3 | "version": "2.0.3",
4 | "dependencies": {
5 | "jquery": "~1.7 || ~2 || ~3"
6 | },
7 | "main": "jquery-observe.js",
8 | "homepage": "https://github.com/kapetan/jquery-observe",
9 | "author": "Mirza Kapetanovic",
10 | "description": "Observe DOM mutations with jQuery",
11 | "keywords": [
12 | "observe",
13 | "observer",
14 | "jquery",
15 | "dom",
16 | "compatibility"
17 | ],
18 | "license": "MIT",
19 | "ignore": [
20 | "bower.json",
21 | "README.md",
22 | "test",
23 | "index.html",
24 | "**/.*"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | jQuery Observe
5 |
6 |
7 |
8 |
9 |
10 |
27 |
28 |
29 |
30 |
31 |
32 |
Item 1
33 |
Item 2
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/jquery-observe.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | $.Observe = {};
3 | }(jQuery));
4 |
5 | (function($, ns) {
6 | var get = function(origin, target) {
7 | if(!target) {
8 | target = origin;
9 | origin = window.document;
10 | }
11 |
12 | var result = [];
13 |
14 | $(target).each(function() {
15 | var selector = [];
16 | var prev = $(this);
17 |
18 | for(var current = prev.parent(); current.length && !prev.is(origin); current = current.parent()) {
19 | var tag = prev.get(0).tagName.toLowerCase();
20 |
21 | selector.push(tag + ':eq(' + current.children(tag).index(prev) + ')');
22 |
23 | prev = current;
24 | }
25 |
26 | if(!current.length && !prev.is(origin)) {
27 | return;
28 | }
29 |
30 | result.push('> ' + selector.reverse().join(' > '));
31 | });
32 |
33 | return result.join(', ');
34 | };
35 |
36 | var capture = function(origin, target) {
37 | if(!target) {
38 | target = origin;
39 | origin = window.document;
40 | }
41 |
42 | var result = [];
43 |
44 | $(target).each(function() {
45 | var textIndex = -1;
46 | var realTarget = this;
47 |
48 | if(this instanceof Text) {
49 | realTarget = this.parentNode;
50 | var children = realTarget.childNodes;
51 |
52 | for(var i = 0; i < children.length; i++) {
53 | if(children[i] === this) {
54 | textIndex = i;
55 | break;
56 | }
57 | }
58 | }
59 |
60 | var path = get(origin, realTarget);
61 | var same = $(origin).is(realTarget);
62 |
63 | result.push(function(origin) {
64 | var target = same ? origin : $(origin).find(path);
65 | return textIndex === -1 ? target : target.contents()[textIndex];
66 | });
67 | });
68 |
69 | return function(origin) {
70 | origin = origin || window.document;
71 |
72 | return result.reduce(function(acc, fn) {
73 | return acc.add(fn(origin));
74 | }, $([]));
75 | };
76 | };
77 |
78 | ns.path = {
79 | get: get,
80 | capture: capture
81 | };
82 | }(jQuery, jQuery.Observe));
83 |
84 | (function($, ns) {
85 | var Branch = function(root) {
86 | this.original = $(root);
87 | this.root = this.original.clone(false, true);
88 | };
89 |
90 | Branch.prototype.find = function(selector) {
91 | var path = ns.path.capture(this.original, selector);
92 | return path(this.root);
93 | };
94 |
95 | ns.Branch = Branch;
96 | }(jQuery, jQuery.Observe));
97 |
98 | (function($, ns) {
99 | var toObject = function(array, fn) {
100 | var result = {};
101 |
102 | array.forEach(function(name) {
103 | var pair = fn(name);
104 |
105 | if(pair) {
106 | result[pair[0]] = pair[1];
107 | }
108 | });
109 |
110 | return result;
111 | };
112 |
113 | var OBSERVER_OPTIONS = toObject([
114 | 'childList',
115 | 'attributes',
116 | 'characterData',
117 | 'subtree',
118 | 'attributeOldValue',
119 | 'characterDataOldValue',
120 | 'attributeFilter'
121 | ], function(name) {
122 | return [name.toLowerCase(), name];
123 | });
124 | var ALL = toObject(Object.keys(OBSERVER_OPTIONS), function(name) {
125 | if(name !== 'attributefilter') {
126 | return [OBSERVER_OPTIONS[name], true];
127 | }
128 | });
129 | var EXTENDED_OPTIONS = toObject([
130 | 'added',
131 | 'removed'
132 | ], function(name) {
133 | return [name.toLowerCase(), name];
134 | });
135 |
136 | var EMPTY = $([]);
137 |
138 | var parseOptions = function(options) {
139 | if(typeof options === 'object') {
140 | return options;
141 | }
142 |
143 | options = options.split(/\s+/);
144 |
145 | var result = {};
146 |
147 | options.forEach(function(opt) {
148 | opt = opt.toLowerCase();
149 |
150 | if(!OBSERVER_OPTIONS[opt] && !EXTENDED_OPTIONS[opt]) {
151 | throw new Error('Unknown option ' + opt);
152 | }
153 |
154 | result[OBSERVER_OPTIONS[opt] || EXTENDED_OPTIONS[opt]] = true;
155 | });
156 |
157 | return result;
158 | };
159 |
160 | var objectToString = function(obj) {
161 | return '[' + Object.keys(obj).sort().reduce(function(acc, key) {
162 | var valueStr = (obj[key] && typeof obj[key] === 'object') ? objectToString(obj[key]) : obj[key];
163 |
164 | return acc + '[' + JSON.stringify(key) + ':' + valueStr + ']';
165 | }, '') + ']';
166 | };
167 |
168 | var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
169 |
170 | var Pattern = function(target, options, selector, handler) {
171 | this._originalOptions = $.extend({}, options);
172 | options = $.extend({}, options);
173 |
174 | this.attributeFilter = options.attributeFilter;
175 |
176 | delete options.attributeFilter;
177 |
178 | if(selector) {
179 | options.subtree = true;
180 | }
181 | if(options.childList) {
182 | options.added = true;
183 | options.removed = true;
184 | }
185 | if(options.added || options.removed) {
186 | options.childList = true;
187 | }
188 |
189 | this.target = $(target);
190 | this.options = options;
191 | this.selector = selector;
192 | this.handler = handler;
193 | };
194 | Pattern.prototype.is = function(options, selector, handler) {
195 | return objectToString(this._originalOptions) === objectToString(options) &&
196 | this.selector === selector &&
197 | this.handler === handler;
198 | };
199 | Pattern.prototype.match = function(record) {
200 | var self = this;
201 | var options = this.options;
202 | var type = record.type;
203 |
204 | if(!this.options[type]) {
205 | return EMPTY;
206 | }
207 |
208 | if(this.selector) {
209 | switch(type) {
210 | case 'attributes':
211 | if(!this._matchAttributeFilter(record)) {
212 | break;
213 | }
214 | case 'characterData':
215 | return this._matchAttributesAndCharacterData(record);
216 | case 'childList':
217 | if(record.addedNodes && record.addedNodes.length && options.added) {
218 | var result = this._matchAddedNodes(record);
219 |
220 | if(result.length) {
221 | return result;
222 | }
223 | }
224 | if(record.removedNodes && record.removedNodes.length && options.removed) {
225 | return this._matchRemovedNodes(record);
226 | }
227 | }
228 | } else {
229 | var recordTarget = record.target instanceof Text ?
230 | $(record.target).parent() : $(record.target);
231 |
232 | if(!options.subtree && recordTarget.get(0) !== this.target.get(0)) {
233 | return EMPTY;
234 | }
235 |
236 | switch(type) {
237 | case 'attributes':
238 | if(!this._matchAttributeFilter(record)) {
239 | break;
240 | }
241 | case 'characterData':
242 | return this.target;
243 | case 'childList':
244 | if((record.addedNodes && record.addedNodes.length && options.added) ||
245 | (record.removedNodes && record.removedNodes.length && options.removed)) {
246 |
247 | return this.target;
248 | }
249 | }
250 | }
251 |
252 | return EMPTY;
253 | };
254 | Pattern.prototype._matchAttributesAndCharacterData = function(record) {
255 | return this._matchSelector(this.target, [record.target]);
256 | };
257 | Pattern.prototype._matchAddedNodes = function(record) {
258 | return this._matchSelector(this.target, record.addedNodes);
259 | };
260 | Pattern.prototype._matchRemovedNodes = function(record) {
261 | var branch = new ns.Branch(this.target);
262 | var nodes = Array.prototype.slice.call(record.removedNodes).map(function(node) {
263 | return node.cloneNode(true);
264 | });
265 |
266 | if(record.previousSibling) {
267 | branch.find(record.previousSibling).after(nodes);
268 | } else if(record.nextSibling) {
269 | branch.find(record.nextSibling).before(nodes);
270 | } else {
271 | branch.find(record.target).empty().append(nodes);
272 | }
273 |
274 | return this._matchSelector(branch.root, nodes).length ? $(record.target) : EMPTY;
275 | };
276 | Pattern.prototype._matchSelector = function(origin, element) {
277 | var match = origin.find(this.selector);
278 | element = Array.prototype.slice.call(element);
279 |
280 | match = match.filter(function() {
281 | var self = this;
282 |
283 | return element.some(function(node) {
284 | if(node instanceof Text) return node.parentNode === self;
285 | else return node === self || $(node).has(self).length;
286 | });
287 | });
288 |
289 | return match;
290 | };
291 | Pattern.prototype._matchAttributeFilter = function(record) {
292 | if(this.attributeFilter && this.attributeFilter.length) {
293 | return this.attributeFilter.indexOf(record.attributeName) >= 0;
294 | }
295 |
296 | return true;
297 | };
298 |
299 | var Observer = function(target) {
300 | this.patterns = [];
301 |
302 | this._target = target;
303 | this._observer = null;
304 | };
305 | Observer.prototype.observe = function(options, selector, handler) {
306 | var self = this;
307 |
308 | if(!this._observer) {
309 | this._observer = new MutationObserver(function(records) {
310 | records.forEach(function(record) {
311 | self.patterns.forEach(function(pattern) {
312 | var match = pattern.match(record);
313 |
314 | if(match.length) {
315 | match.each(function() {
316 | pattern.handler.call(this, record);
317 | });
318 | }
319 | });
320 | });
321 | });
322 | } else {
323 | this._observer.disconnect();
324 | }
325 |
326 | this.patterns.push(new Pattern(this._target, options, selector, handler));
327 | this._observer.observe(this._target, this._collapseOptions());
328 | };
329 | Observer.prototype.disconnect = function(options, selector, handler) {
330 | var self = this;
331 |
332 | if(this._observer) {
333 | this.patterns.filter(function(pattern) {
334 | return pattern.is(options, selector, handler);
335 | }).forEach(function(pattern) {
336 | var index = self.patterns.indexOf(pattern);
337 |
338 | self.patterns.splice(index, 1);
339 | });
340 |
341 | if(!this.patterns.length) {
342 | this._observer.disconnect();
343 | }
344 | }
345 | };
346 | Observer.prototype.disconnectAll = function() {
347 | if(this._observer) {
348 | this.patterns = [];
349 | this._observer.disconnect();
350 | }
351 | };
352 | Observer.prototype.pause = function() {
353 | if(this._observer) {
354 | this._observer.disconnect();
355 | }
356 | };
357 | Observer.prototype.resume = function() {
358 | if(this._observer) {
359 | this._observer.observe(this._target, this._collapseOptions());
360 | }
361 | };
362 | Observer.prototype._collapseOptions = function() {
363 | var result = {};
364 |
365 | this.patterns.forEach(function(pattern) {
366 | var restrictiveFilter = result.attributes && result.attributeFilter;
367 |
368 | if((restrictiveFilter || !result.attributes) && pattern.attributeFilter) {
369 | var attributeFilter = (result.attributeFilter || []).concat(pattern.attributeFilter);
370 | var existing = {};
371 | var unique = [];
372 |
373 | attributeFilter.forEach(function(attr) {
374 | if(!existing[attr]) {
375 | unique.push(attr);
376 | existing[attr] = 1;
377 | }
378 | });
379 |
380 | result.attributeFilter = unique;
381 | } else if(restrictiveFilter && pattern.options.attributes && !pattern.attributeFilter) {
382 | delete result.attributeFilter;
383 | }
384 |
385 | $.extend(result, pattern.options);
386 | });
387 |
388 | Object.keys(EXTENDED_OPTIONS).forEach(function(name) {
389 | delete result[EXTENDED_OPTIONS[name]];
390 | });
391 |
392 | return result;
393 | };
394 |
395 | var DOMEventObserver = function(target) {
396 | this.patterns = [];
397 |
398 | this._paused = false;
399 | this._target = target;
400 | this._events = {};
401 | this._handler = this._handler.bind(this);
402 | };
403 | DOMEventObserver.prototype.NS = '.jQueryObserve';
404 | DOMEventObserver.prototype.observe = function(options, selector, handler) {
405 | var pattern = new Pattern(this._target, options, selector, handler);
406 | var target = $(this._target);
407 |
408 | if(pattern.options.childList) {
409 | this._addEvent('DOMNodeInserted');
410 | this._addEvent('DOMNodeRemoved');
411 | }
412 | if(pattern.options.attributes) {
413 | this._addEvent('DOMAttrModified');
414 | }
415 | if(pattern.options.characterData) {
416 | this._addEvent('DOMCharacerDataModified');
417 | }
418 |
419 | this.patterns.push(pattern);
420 | };
421 | DOMEventObserver.prototype.disconnect = function(options, selector, handler) {
422 | var target = $(this._target);
423 | var self = this;
424 |
425 | this.patterns.filter(function(pattern) {
426 | return pattern.is(options, selector, handler);
427 | }).forEach(function(pattern) {
428 | var index = self.patterns.indexOf(pattern);
429 |
430 | self.patterns.splice(index, 1);
431 | });
432 |
433 | var eventsInUse = this.patterns.reduce(function(acc, pattern) {
434 | if(pattern.options.childList) {
435 | acc.DOMNodeInserted = true;
436 | acc.DOMNodeRemoved = true;
437 | }
438 | if(pattern.options.attributes) {
439 | acc.DOMAttrModified = true;
440 | }
441 | if(pattern.options.characterData) {
442 | acc.DOMCharacerDataModified = true;
443 | }
444 |
445 | return acc;
446 | }, {});
447 |
448 | Object.keys(this._events).forEach(function(type) {
449 | if(eventsInUse[type]) {
450 | return;
451 | }
452 |
453 | delete self._events[type];
454 |
455 | target.off(type + self.NS, self._handler);
456 | });
457 | };
458 | DOMEventObserver.prototype.disconnectAll = function() {
459 | var target = $(this._target);
460 |
461 | for(var name in this._events) {
462 | target.off(name + this.NS, this._handler);
463 | }
464 |
465 | this._events = {};
466 | this.patterns = [];
467 | };
468 | DOMEventObserver.prototype.pause = function() {
469 | this._paused = true;
470 | };
471 | DOMEventObserver.prototype.resume = function() {
472 | this._paused = false;
473 | };
474 | DOMEventObserver.prototype._handler = function(e) {
475 | if(this._paused) {
476 | return;
477 | }
478 |
479 | var record = {
480 | type: null,
481 | target: null,
482 | addedNodes: null,
483 | removedNodes: null,
484 | previousSibling: null,
485 | nextSibling: null,
486 | attributeName: null,
487 | attributeNamespace: null,
488 | oldValue: null
489 | };
490 |
491 | switch(e.type) {
492 | case 'DOMAttrModified':
493 | record.type = 'attributes';
494 | record.target = e.target;
495 | record.attributeName = e.attrName;
496 | record.oldValue = e.prevValue;
497 |
498 | break;
499 | case 'DOMCharacerDataModified':
500 | record.type = 'characterData';
501 | record.target = $(e.target).parent().get(0);
502 | record.attributeName = e.attrName;
503 | record.oldValue = e.prevValue;
504 |
505 | break;
506 | case 'DOMNodeInserted':
507 | record.type = 'childList';
508 | record.target = e.relatedNode;
509 | record.addedNodes = [e.target];
510 | record.removedNodes = [];
511 |
512 | break;
513 | case 'DOMNodeRemoved':
514 | record.type = 'childList';
515 | record.target = e.relatedNode;
516 | record.addedNodes = [];
517 | record.removedNodes = [e.target];
518 |
519 | break;
520 | }
521 |
522 | for(var i = 0; i < this.patterns.length; i++) {
523 | var pattern = this.patterns[i];
524 | var match = pattern.match(record);
525 |
526 | if(match.length) {
527 | match.each(function() {
528 | pattern.handler.call(this, record);
529 | });
530 | }
531 | }
532 | };
533 | DOMEventObserver.prototype._addEvent = function(type) {
534 | if(!this._events[type]) {
535 | $(this._target).on(type + this.NS, this._handler);
536 | this._events[type] = true;
537 | }
538 | };
539 |
540 | ns.Pattern = Pattern;
541 | ns.MutationObserver = Observer;
542 | ns.DOMEventObserver = DOMEventObserver;
543 |
544 | $.fn.observe = function(options, selector, handler) {
545 | if(!selector) {
546 | handler = options;
547 | options = ALL;
548 | } else if(!handler) {
549 | handler = selector;
550 | selector = null;
551 | }
552 |
553 | return this.each(function() {
554 | var self = $(this);
555 | var observer = self.data('observer');
556 |
557 | if(!observer) {
558 | if(MutationObserver) {
559 | observer = new Observer(this);
560 | } else {
561 | observer = new DOMEventObserver(this);
562 | }
563 |
564 | self.data('observer', observer);
565 | }
566 |
567 | options = parseOptions(options);
568 |
569 | observer.observe(options, selector, handler);
570 | });
571 | };
572 | $.fn.disconnect = function(options, selector, handler) {
573 | if(!options) {
574 | // No arguments
575 | }
576 | else if(!selector) {
577 | handler = options;
578 | options = ALL;
579 | } else if(!handler) {
580 | handler = selector;
581 | selector = null;
582 | }
583 |
584 | return this.each(function() {
585 | var self = $(this);
586 | var observer = self.data('observer');
587 |
588 | if(!observer) {
589 | return;
590 | }
591 |
592 | if(!options) {
593 | observer.disconnectAll();
594 | self.removeData('observer');
595 |
596 | return;
597 | }
598 |
599 | options = parseOptions(options);
600 |
601 | observer.disconnect(options, selector, handler);
602 | });
603 | };
604 | }(jQuery, jQuery.Observe));
605 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@kapetan/jquery-observe",
3 | "version": "3.0.0",
4 | "description": "Observe DOM mutations with jQuery",
5 | "main": "jquery-observe.js",
6 | "dependencies": {
7 | "jquery": "~1.7 || ~2 || ~3"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/kapetan/jquery-observe.git"
12 | },
13 | "keywords": [
14 | "observe",
15 | "observer",
16 | "jquery",
17 | "dom",
18 | "compatibility"
19 | ],
20 | "author": "Mirza Kapetanovic",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/kapetan/jquery-observe/issues"
24 | },
25 | "homepage": "https://github.com/kapetan/jquery-observe#readme"
26 | }
27 |
--------------------------------------------------------------------------------
/test/benchmark.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Benchmark.js v1.0.0
3 | * Copyright 2010-2012 Mathias Bynens
4 | * Based on JSLitmus.js, copyright Robert Kieffer
5 | * Modified by John-David Dalton
6 | * Available under MIT license
7 | */
8 | ;(function(window, undefined) {
9 | 'use strict';
10 |
11 | /** Used to assign each benchmark an incrimented id */
12 | var counter = 0;
13 |
14 | /** Detect DOM document object */
15 | var doc = isHostType(window, 'document') && document;
16 |
17 | /** Detect free variable `define` */
18 | var freeDefine = typeof define == 'function' &&
19 | typeof define.amd == 'object' && define.amd && define;
20 |
21 | /** Detect free variable `exports` */
22 | var freeExports = typeof exports == 'object' && exports &&
23 | (typeof global == 'object' && global && global == global.global && (window = global), exports);
24 |
25 | /** Detect free variable `require` */
26 | var freeRequire = typeof require == 'function' && require;
27 |
28 | /** Used to crawl all properties regardless of enumerability */
29 | var getAllKeys = Object.getOwnPropertyNames;
30 |
31 | /** Used to get property descriptors */
32 | var getDescriptor = Object.getOwnPropertyDescriptor;
33 |
34 | /** Used in case an object doesn't have its own method */
35 | var hasOwnProperty = {}.hasOwnProperty;
36 |
37 | /** Used to check if an object is extensible */
38 | var isExtensible = Object.isExtensible || function() { return true; };
39 |
40 | /** Used to access Wade Simmons' Node microtime module */
41 | var microtimeObject = req('microtime');
42 |
43 | /** Used to access the browser's high resolution timer */
44 | var perfObject = isHostType(window, 'performance') && performance;
45 |
46 | /** Used to call the browser's high resolution timer */
47 | var perfName = perfObject && (
48 | perfObject.now && 'now' ||
49 | perfObject.webkitNow && 'webkitNow'
50 | );
51 |
52 | /** Used to access Node's high resolution timer */
53 | var processObject = isHostType(window, 'process') && process;
54 |
55 | /** Used to check if an own property is enumerable */
56 | var propertyIsEnumerable = {}.propertyIsEnumerable;
57 |
58 | /** Used to set property descriptors */
59 | var setDescriptor = Object.defineProperty;
60 |
61 | /** Used to resolve a value's internal [[Class]] */
62 | var toString = {}.toString;
63 |
64 | /** Used to prevent a `removeChild` memory leak in IE < 9 */
65 | var trash = doc && doc.createElement('div');
66 |
67 | /** Used to integrity check compiled tests */
68 | var uid = 'uid' + (+new Date);
69 |
70 | /** Used to avoid infinite recursion when methods call each other */
71 | var calledBy = {};
72 |
73 | /** Used to avoid hz of Infinity */
74 | var divisors = {
75 | '1': 4096,
76 | '2': 512,
77 | '3': 64,
78 | '4': 8,
79 | '5': 0
80 | };
81 |
82 | /**
83 | * T-Distribution two-tailed critical values for 95% confidence
84 | * http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm
85 | */
86 | var tTable = {
87 | '1': 12.706,'2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447,
88 | '7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179,
89 | '13': 2.16, '14': 2.145, '15': 2.131, '16': 2.12, '17': 2.11, '18': 2.101,
90 | '19': 2.093, '20': 2.086, '21': 2.08, '22': 2.074, '23': 2.069, '24': 2.064,
91 | '25': 2.06, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042,
92 | 'infinity': 1.96
93 | };
94 |
95 | /**
96 | * Critical Mann-Whitney U-values for 95% confidence
97 | * http://www.saburchill.com/IBbiology/stats/003.html
98 | */
99 | var uTable = {
100 | '5': [0, 1, 2],
101 | '6': [1, 2, 3, 5],
102 | '7': [1, 3, 5, 6, 8],
103 | '8': [2, 4, 6, 8, 10, 13],
104 | '9': [2, 4, 7, 10, 12, 15, 17],
105 | '10': [3, 5, 8, 11, 14, 17, 20, 23],
106 | '11': [3, 6, 9, 13, 16, 19, 23, 26, 30],
107 | '12': [4, 7, 11, 14, 18, 22, 26, 29, 33, 37],
108 | '13': [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45],
109 | '14': [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55],
110 | '15': [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64],
111 | '16': [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75],
112 | '17': [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87],
113 | '18': [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99],
114 | '19': [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113],
115 | '20': [8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127],
116 | '21': [8, 15, 22, 29, 36, 43, 50, 58, 65, 73, 80, 88, 96, 103, 111, 119, 126, 134, 142],
117 | '22': [9, 16, 23, 30, 38, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 150, 158],
118 | '23': [9, 17, 24, 32, 40, 48, 56, 64, 73, 81, 89, 98, 106, 115, 123, 132, 140, 149, 157, 166, 175],
119 | '24': [10, 17, 25, 33, 42, 50, 59, 67, 76, 85, 94, 102, 111, 120, 129, 138, 147, 156, 165, 174, 183, 192],
120 | '25': [10, 18, 27, 35, 44, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 145, 154, 163, 173, 182, 192, 201, 211],
121 | '26': [11, 19, 28, 37, 46, 55, 64, 74, 83, 93, 102, 112, 122, 132, 141, 151, 161, 171, 181, 191, 200, 210, 220, 230],
122 | '27': [11, 20, 29, 38, 48, 57, 67, 77, 87, 97, 107, 118, 125, 138, 147, 158, 168, 178, 188, 199, 209, 219, 230, 240, 250],
123 | '28': [12, 21, 30, 40, 50, 60, 70, 80, 90, 101, 111, 122, 132, 143, 154, 164, 175, 186, 196, 207, 218, 228, 239, 250, 261, 272],
124 | '29': [13, 22, 32, 42, 52, 62, 73, 83, 94, 105, 116, 127, 138, 149, 160, 171, 182, 193, 204, 215, 226, 238, 249, 260, 271, 282, 294],
125 | '30': [13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317]
126 | };
127 |
128 | /**
129 | * An object used to flag environments/features.
130 | *
131 | * @static
132 | * @memberOf Benchmark
133 | * @type Object
134 | */
135 | var support = {};
136 |
137 | (function() {
138 |
139 | /**
140 | * Detect Adobe AIR.
141 | *
142 | * @memberOf Benchmark.support
143 | * @type Boolean
144 | */
145 | support.air = isClassOf(window.runtime, 'ScriptBridgingProxyObject');
146 |
147 | /**
148 | * Detect if `arguments` objects have the correct internal [[Class]] value.
149 | *
150 | * @memberOf Benchmark.support
151 | * @type Boolean
152 | */
153 | support.argumentsClass = isClassOf(arguments, 'Arguments');
154 |
155 | /**
156 | * Detect if in a browser environment.
157 | *
158 | * @memberOf Benchmark.support
159 | * @type Boolean
160 | */
161 | support.browser = doc && isHostType(window, 'navigator');
162 |
163 | /**
164 | * Detect if strings support accessing characters by index.
165 | *
166 | * @memberOf Benchmark.support
167 | * @type Boolean
168 | */
169 | support.charByIndex =
170 | // IE 8 supports indexes on string literals but not string objects
171 | ('x'[0] + Object('x')[0]) == 'xx';
172 |
173 | /**
174 | * Detect if strings have indexes as own properties.
175 | *
176 | * @memberOf Benchmark.support
177 | * @type Boolean
178 | */
179 | support.charByOwnIndex =
180 | // Narwhal, Rhino, RingoJS, IE 8, and Opera < 10.52 support indexes on
181 | // strings but don't detect them as own properties
182 | support.charByIndex && hasKey('x', '0');
183 |
184 | /**
185 | * Detect if Java is enabled/exposed.
186 | *
187 | * @memberOf Benchmark.support
188 | * @type Boolean
189 | */
190 | support.java = isClassOf(window.java, 'JavaPackage');
191 |
192 | /**
193 | * Detect if the Timers API exists.
194 | *
195 | * @memberOf Benchmark.support
196 | * @type Boolean
197 | */
198 | support.timeout = isHostType(window, 'setTimeout') && isHostType(window, 'clearTimeout');
199 |
200 | /**
201 | * Detect if functions support decompilation.
202 | *
203 | * @name decompilation
204 | * @memberOf Benchmark.support
205 | * @type Boolean
206 | */
207 | try {
208 | // Safari 2.x removes commas in object literals
209 | // from Function#toString results
210 | // http://webk.it/11609
211 | // Firefox 3.6 and Opera 9.25 strip grouping
212 | // parentheses from Function#toString results
213 | // http://bugzil.la/559438
214 | support.decompilation = Function(
215 | 'return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')'
216 | )()(0).x === '1';
217 | } catch(e) {
218 | support.decompilation = false;
219 | }
220 |
221 | /**
222 | * Detect ES5+ property descriptor API.
223 | *
224 | * @name descriptors
225 | * @memberOf Benchmark.support
226 | * @type Boolean
227 | */
228 | try {
229 | var o = {};
230 | support.descriptors = (setDescriptor(o, o, o), 'value' in getDescriptor(o, o));
231 | } catch(e) {
232 | support.descriptors = false;
233 | }
234 |
235 | /**
236 | * Detect ES5+ Object.getOwnPropertyNames().
237 | *
238 | * @name getAllKeys
239 | * @memberOf Benchmark.support
240 | * @type Boolean
241 | */
242 | try {
243 | support.getAllKeys = /\bvalueOf\b/.test(getAllKeys(Object.prototype));
244 | } catch(e) {
245 | support.getAllKeys = false;
246 | }
247 |
248 | /**
249 | * Detect if own properties are iterated before inherited properties (all but IE < 9).
250 | *
251 | * @name iteratesOwnLast
252 | * @memberOf Benchmark.support
253 | * @type Boolean
254 | */
255 | support.iteratesOwnFirst = (function() {
256 | var props = [];
257 | function ctor() { this.x = 1; }
258 | ctor.prototype = { 'y': 1 };
259 | for (var prop in new ctor) { props.push(prop); }
260 | return props[0] == 'x';
261 | }());
262 |
263 | /**
264 | * Detect if a node's [[Class]] is resolvable (all but IE < 9)
265 | * and that the JS engine errors when attempting to coerce an object to a
266 | * string without a `toString` property value of `typeof` "function".
267 | *
268 | * @name nodeClass
269 | * @memberOf Benchmark.support
270 | * @type Boolean
271 | */
272 | try {
273 | support.nodeClass = ({ 'toString': 0 } + '', toString.call(doc || 0) != '[object Object]');
274 | } catch(e) {
275 | support.nodeClass = true;
276 | }
277 | }());
278 |
279 | /**
280 | * Timer object used by `clock()` and `Deferred#resolve`.
281 | *
282 | * @private
283 | * @type Object
284 | */
285 | var timer = {
286 |
287 | /**
288 | * The timer namespace object or constructor.
289 | *
290 | * @private
291 | * @memberOf timer
292 | * @type Function|Object
293 | */
294 | 'ns': Date,
295 |
296 | /**
297 | * Starts the deferred timer.
298 | *
299 | * @private
300 | * @memberOf timer
301 | * @param {Object} deferred The deferred instance.
302 | */
303 | 'start': null, // lazy defined in `clock()`
304 |
305 | /**
306 | * Stops the deferred timer.
307 | *
308 | * @private
309 | * @memberOf timer
310 | * @param {Object} deferred The deferred instance.
311 | */
312 | 'stop': null // lazy defined in `clock()`
313 | };
314 |
315 | /** Shortcut for inverse results */
316 | var noArgumentsClass = !support.argumentsClass,
317 | noCharByIndex = !support.charByIndex,
318 | noCharByOwnIndex = !support.charByOwnIndex;
319 |
320 | /** Math shortcuts */
321 | var abs = Math.abs,
322 | floor = Math.floor,
323 | max = Math.max,
324 | min = Math.min,
325 | pow = Math.pow,
326 | sqrt = Math.sqrt;
327 |
328 | /*--------------------------------------------------------------------------*/
329 |
330 | /**
331 | * The Benchmark constructor.
332 | *
333 | * @constructor
334 | * @param {String} name A name to identify the benchmark.
335 | * @param {Function|String} fn The test to benchmark.
336 | * @param {Object} [options={}] Options object.
337 | * @example
338 | *
339 | * // basic usage (the `new` operator is optional)
340 | * var bench = new Benchmark(fn);
341 | *
342 | * // or using a name first
343 | * var bench = new Benchmark('foo', fn);
344 | *
345 | * // or with options
346 | * var bench = new Benchmark('foo', fn, {
347 | *
348 | * // displayed by Benchmark#toString if `name` is not available
349 | * 'id': 'xyz',
350 | *
351 | * // called when the benchmark starts running
352 | * 'onStart': onStart,
353 | *
354 | * // called after each run cycle
355 | * 'onCycle': onCycle,
356 | *
357 | * // called when aborted
358 | * 'onAbort': onAbort,
359 | *
360 | * // called when a test errors
361 | * 'onError': onError,
362 | *
363 | * // called when reset
364 | * 'onReset': onReset,
365 | *
366 | * // called when the benchmark completes running
367 | * 'onComplete': onComplete,
368 | *
369 | * // compiled/called before the test loop
370 | * 'setup': setup,
371 | *
372 | * // compiled/called after the test loop
373 | * 'teardown': teardown
374 | * });
375 | *
376 | * // or name and options
377 | * var bench = new Benchmark('foo', {
378 | *
379 | * // a flag to indicate the benchmark is deferred
380 | * 'defer': true,
381 | *
382 | * // benchmark test function
383 | * 'fn': function(deferred) {
384 | * // call resolve() when the deferred test is finished
385 | * deferred.resolve();
386 | * }
387 | * });
388 | *
389 | * // or options only
390 | * var bench = new Benchmark({
391 | *
392 | * // benchmark name
393 | * 'name': 'foo',
394 | *
395 | * // benchmark test as a string
396 | * 'fn': '[1,2,3,4].sort()'
397 | * });
398 | *
399 | * // a test's `this` binding is set to the benchmark instance
400 | * var bench = new Benchmark('foo', function() {
401 | * 'My name is '.concat(this.name); // My name is foo
402 | * });
403 | */
404 | function Benchmark(name, fn, options) {
405 | var me = this;
406 |
407 | // allow instance creation without the `new` operator
408 | if (me == null || me.constructor != Benchmark) {
409 | return new Benchmark(name, fn, options);
410 | }
411 | // juggle arguments
412 | if (isClassOf(name, 'Object')) {
413 | // 1 argument (options)
414 | options = name;
415 | }
416 | else if (isClassOf(name, 'Function')) {
417 | // 2 arguments (fn, options)
418 | options = fn;
419 | fn = name;
420 | }
421 | else if (isClassOf(fn, 'Object')) {
422 | // 2 arguments (name, options)
423 | options = fn;
424 | fn = null;
425 | me.name = name;
426 | }
427 | else {
428 | // 3 arguments (name, fn [, options])
429 | me.name = name;
430 | }
431 | setOptions(me, options);
432 | me.id || (me.id = ++counter);
433 | me.fn == null && (me.fn = fn);
434 | me.stats = deepClone(me.stats);
435 | me.times = deepClone(me.times);
436 | }
437 |
438 | /**
439 | * The Deferred constructor.
440 | *
441 | * @constructor
442 | * @memberOf Benchmark
443 | * @param {Object} clone The cloned benchmark instance.
444 | */
445 | function Deferred(clone) {
446 | var me = this;
447 | if (me == null || me.constructor != Deferred) {
448 | return new Deferred(clone);
449 | }
450 | me.benchmark = clone;
451 | clock(me);
452 | }
453 |
454 | /**
455 | * The Event constructor.
456 | *
457 | * @constructor
458 | * @memberOf Benchmark
459 | * @param {String|Object} type The event type.
460 | */
461 | function Event(type) {
462 | var me = this;
463 | return (me == null || me.constructor != Event)
464 | ? new Event(type)
465 | : (type instanceof Event)
466 | ? type
467 | : extend(me, { 'timeStamp': +new Date }, typeof type == 'string' ? { 'type': type } : type);
468 | }
469 |
470 | /**
471 | * The Suite constructor.
472 | *
473 | * @constructor
474 | * @memberOf Benchmark
475 | * @param {String} name A name to identify the suite.
476 | * @param {Object} [options={}] Options object.
477 | * @example
478 | *
479 | * // basic usage (the `new` operator is optional)
480 | * var suite = new Benchmark.Suite;
481 | *
482 | * // or using a name first
483 | * var suite = new Benchmark.Suite('foo');
484 | *
485 | * // or with options
486 | * var suite = new Benchmark.Suite('foo', {
487 | *
488 | * // called when the suite starts running
489 | * 'onStart': onStart,
490 | *
491 | * // called between running benchmarks
492 | * 'onCycle': onCycle,
493 | *
494 | * // called when aborted
495 | * 'onAbort': onAbort,
496 | *
497 | * // called when a test errors
498 | * 'onError': onError,
499 | *
500 | * // called when reset
501 | * 'onReset': onReset,
502 | *
503 | * // called when the suite completes running
504 | * 'onComplete': onComplete
505 | * });
506 | */
507 | function Suite(name, options) {
508 | var me = this;
509 |
510 | // allow instance creation without the `new` operator
511 | if (me == null || me.constructor != Suite) {
512 | return new Suite(name, options);
513 | }
514 | // juggle arguments
515 | if (isClassOf(name, 'Object')) {
516 | // 1 argument (options)
517 | options = name;
518 | } else {
519 | // 2 arguments (name [, options])
520 | me.name = name;
521 | }
522 | setOptions(me, options);
523 | }
524 |
525 | /*--------------------------------------------------------------------------*/
526 |
527 | /**
528 | * Note: Some array methods have been implemented in plain JavaScript to avoid
529 | * bugs in IE, Opera, Rhino, and Mobile Safari.
530 | *
531 | * IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()`
532 | * functions that fail to remove the last element, `object[0]`, of
533 | * array-like-objects even though the `length` property is set to `0`.
534 | * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
535 | * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
536 | *
537 | * In Opera < 9.50 and some older/beta Mobile Safari versions using `unshift()`
538 | * generically to augment the `arguments` object will pave the value at index 0
539 | * without incrimenting the other values's indexes.
540 | * https://github.com/documentcloud/underscore/issues/9
541 | *
542 | * Rhino and environments it powers, like Narwhal and RingoJS, may have
543 | * buggy Array `concat()`, `reverse()`, `shift()`, `slice()`, `splice()` and
544 | * `unshift()` functions that make sparse arrays non-sparse by assigning the
545 | * undefined indexes a value of undefined.
546 | * https://github.com/mozilla/rhino/commit/702abfed3f8ca043b2636efd31c14ba7552603dd
547 | */
548 |
549 | /**
550 | * Creates an array containing the elements of the host array followed by the
551 | * elements of each argument in order.
552 | *
553 | * @memberOf Benchmark.Suite
554 | * @returns {Array} The new array.
555 | */
556 | function concat() {
557 | var value,
558 | j = -1,
559 | length = arguments.length,
560 | result = slice.call(this),
561 | index = result.length;
562 |
563 | while (++j < length) {
564 | value = arguments[j];
565 | if (isClassOf(value, 'Array')) {
566 | for (var k = 0, l = value.length; k < l; k++, index++) {
567 | if (k in value) {
568 | result[index] = value[k];
569 | }
570 | }
571 | } else {
572 | result[index++] = value;
573 | }
574 | }
575 | return result;
576 | }
577 |
578 | /**
579 | * Utility function used by `shift()`, `splice()`, and `unshift()`.
580 | *
581 | * @private
582 | * @param {Number} start The index to start inserting elements.
583 | * @param {Number} deleteCount The number of elements to delete from the insert point.
584 | * @param {Array} elements The elements to insert.
585 | * @returns {Array} An array of deleted elements.
586 | */
587 | function insert(start, deleteCount, elements) {
588 | // `result` should have its length set to the `deleteCount`
589 | // see https://bugs.ecmascript.org/show_bug.cgi?id=332
590 | var deleteEnd = start + deleteCount,
591 | elementCount = elements ? elements.length : 0,
592 | index = start - 1,
593 | length = start + elementCount,
594 | object = this,
595 | result = Array(deleteCount),
596 | tail = slice.call(object, deleteEnd);
597 |
598 | // delete elements from the array
599 | while (++index < deleteEnd) {
600 | if (index in object) {
601 | result[index - start] = object[index];
602 | delete object[index];
603 | }
604 | }
605 | // insert elements
606 | index = start - 1;
607 | while (++index < length) {
608 | object[index] = elements[index - start];
609 | }
610 | // append tail elements
611 | start = index--;
612 | length = max(0, (object.length >>> 0) - deleteCount + elementCount);
613 | while (++index < length) {
614 | if ((index - start) in tail) {
615 | object[index] = tail[index - start];
616 | } else if (index in object) {
617 | delete object[index];
618 | }
619 | }
620 | // delete excess elements
621 | deleteCount = deleteCount > elementCount ? deleteCount - elementCount : 0;
622 | while (deleteCount--) {
623 | index = length + deleteCount;
624 | if (index in object) {
625 | delete object[index];
626 | }
627 | }
628 | object.length = length;
629 | return result;
630 | }
631 |
632 | /**
633 | * Rearrange the host array's elements in reverse order.
634 | *
635 | * @memberOf Benchmark.Suite
636 | * @returns {Array} The reversed array.
637 | */
638 | function reverse() {
639 | var upperIndex,
640 | value,
641 | index = -1,
642 | object = Object(this),
643 | length = object.length >>> 0,
644 | middle = floor(length / 2);
645 |
646 | if (length > 1) {
647 | while (++index < middle) {
648 | upperIndex = length - index - 1;
649 | value = upperIndex in object ? object[upperIndex] : uid;
650 | if (index in object) {
651 | object[upperIndex] = object[index];
652 | } else {
653 | delete object[upperIndex];
654 | }
655 | if (value != uid) {
656 | object[index] = value;
657 | } else {
658 | delete object[index];
659 | }
660 | }
661 | }
662 | return object;
663 | }
664 |
665 | /**
666 | * Removes the first element of the host array and returns it.
667 | *
668 | * @memberOf Benchmark.Suite
669 | * @returns {Mixed} The first element of the array.
670 | */
671 | function shift() {
672 | return insert.call(this, 0, 1)[0];
673 | }
674 |
675 | /**
676 | * Creates an array of the host array's elements from the start index up to,
677 | * but not including, the end index.
678 | *
679 | * @memberOf Benchmark.Suite
680 | * @param {Number} start The starting index.
681 | * @param {Number} end The end index.
682 | * @returns {Array} The new array.
683 | */
684 | function slice(start, end) {
685 | var index = -1,
686 | object = Object(this),
687 | length = object.length >>> 0,
688 | result = [];
689 |
690 | start = toInteger(start);
691 | start = start < 0 ? max(length + start, 0) : min(start, length);
692 | start--;
693 | end = end == null ? length : toInteger(end);
694 | end = end < 0 ? max(length + end, 0) : min(end, length);
695 |
696 | while ((++index, ++start) < end) {
697 | if (start in object) {
698 | result[index] = object[start];
699 | }
700 | }
701 | return result;
702 | }
703 |
704 | /**
705 | * Allows removing a range of elements and/or inserting elements into the
706 | * host array.
707 | *
708 | * @memberOf Benchmark.Suite
709 | * @param {Number} start The start index.
710 | * @param {Number} deleteCount The number of elements to delete.
711 | * @param {Mixed} [val1, val2, ...] values to insert at the `start` index.
712 | * @returns {Array} An array of removed elements.
713 | */
714 | function splice(start, deleteCount) {
715 | var object = Object(this),
716 | length = object.length >>> 0;
717 |
718 | start = toInteger(start);
719 | start = start < 0 ? max(length + start, 0) : min(start, length);
720 |
721 | // support the de-facto SpiderMonkey extension
722 | // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice#Parameters
723 | // https://bugs.ecmascript.org/show_bug.cgi?id=429
724 | deleteCount = arguments.length == 1
725 | ? length - start
726 | : min(max(toInteger(deleteCount), 0), length - start);
727 |
728 | return insert.call(object, start, deleteCount, slice.call(arguments, 2));
729 | }
730 |
731 | /**
732 | * Converts the specified `value` to an integer.
733 | *
734 | * @private
735 | * @param {Mixed} value The value to convert.
736 | * @returns {Number} The resulting integer.
737 | */
738 | function toInteger(value) {
739 | value = +value;
740 | return value === 0 || !isFinite(value) ? value || 0 : value - (value % 1);
741 | }
742 |
743 | /**
744 | * Appends arguments to the host array.
745 | *
746 | * @memberOf Benchmark.Suite
747 | * @returns {Number} The new length.
748 | */
749 | function unshift() {
750 | var object = Object(this);
751 | insert.call(object, 0, 0, arguments);
752 | return object.length;
753 | }
754 |
755 | /*--------------------------------------------------------------------------*/
756 |
757 | /**
758 | * A generic `Function#bind` like method.
759 | *
760 | * @private
761 | * @param {Function} fn The function to be bound to `thisArg`.
762 | * @param {Mixed} thisArg The `this` binding for the given function.
763 | * @returns {Function} The bound function.
764 | */
765 | function bind(fn, thisArg) {
766 | return function() { fn.apply(thisArg, arguments); };
767 | }
768 |
769 | /**
770 | * Creates a function from the given arguments string and body.
771 | *
772 | * @private
773 | * @param {String} args The comma separated function arguments.
774 | * @param {String} body The function body.
775 | * @returns {Function} The new function.
776 | */
777 | function createFunction() {
778 | // lazy define
779 | createFunction = function(args, body) {
780 | var result,
781 | anchor = freeDefine ? define.amd : Benchmark,
782 | prop = uid + 'createFunction';
783 |
784 | runScript((freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '=function(' + args + '){' + body + '}');
785 | result = anchor[prop];
786 | delete anchor[prop];
787 | return result;
788 | };
789 | // fix JaegerMonkey bug
790 | // http://bugzil.la/639720
791 | createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || noop)() == uid ? createFunction : Function;
792 | return createFunction.apply(null, arguments);
793 | }
794 |
795 | /**
796 | * Delay the execution of a function based on the benchmark's `delay` property.
797 | *
798 | * @private
799 | * @param {Object} bench The benchmark instance.
800 | * @param {Object} fn The function to execute.
801 | */
802 | function delay(bench, fn) {
803 | bench._timerId = setTimeout(fn, bench.delay * 1e3);
804 | }
805 |
806 | /**
807 | * Destroys the given element.
808 | *
809 | * @private
810 | * @param {Element} element The element to destroy.
811 | */
812 | function destroyElement(element) {
813 | trash.appendChild(element);
814 | trash.innerHTML = '';
815 | }
816 |
817 | /**
818 | * Iterates over an object's properties, executing the `callback` for each.
819 | * Callbacks may terminate the loop by explicitly returning `false`.
820 | *
821 | * @private
822 | * @param {Object} object The object to iterate over.
823 | * @param {Function} callback The function executed per own property.
824 | * @param {Object} options The options object.
825 | * @returns {Object} Returns the object iterated over.
826 | */
827 | function forProps() {
828 | var forShadowed,
829 | skipSeen,
830 | forArgs = true,
831 | shadowed = ['constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf'];
832 |
833 | (function(enumFlag, key) {
834 | // must use a non-native constructor to catch the Safari 2 issue
835 | function Klass() { this.valueOf = 0; };
836 | Klass.prototype.valueOf = 0;
837 | // check various for-in bugs
838 | for (key in new Klass) {
839 | enumFlag += key == 'valueOf' ? 1 : 0;
840 | }
841 | // check if `arguments` objects have non-enumerable indexes
842 | for (key in arguments) {
843 | key == '0' && (forArgs = false);
844 | }
845 | // Safari 2 iterates over shadowed properties twice
846 | // http://replay.waybackmachine.org/20090428222941/http://tobielangel.com/2007/1/29/for-in-loop-broken-in-safari/
847 | skipSeen = enumFlag == 2;
848 | // IE < 9 incorrectly makes an object's properties non-enumerable if they have
849 | // the same name as other non-enumerable properties in its prototype chain.
850 | forShadowed = !enumFlag;
851 | }(0));
852 |
853 | // lazy define
854 | forProps = function(object, callback, options) {
855 | options || (options = {});
856 |
857 | var result = object;
858 | object = Object(object);
859 |
860 | var ctor,
861 | key,
862 | keys,
863 | skipCtor,
864 | done = !result,
865 | which = options.which,
866 | allFlag = which == 'all',
867 | index = -1,
868 | iteratee = object,
869 | length = object.length,
870 | ownFlag = allFlag || which == 'own',
871 | seen = {},
872 | skipProto = isClassOf(object, 'Function'),
873 | thisArg = options.bind;
874 |
875 | if (thisArg !== undefined) {
876 | callback = bind(callback, thisArg);
877 | }
878 | // iterate all properties
879 | if (allFlag && support.getAllKeys) {
880 | for (index = 0, keys = getAllKeys(object), length = keys.length; index < length; index++) {
881 | key = keys[index];
882 | if (callback(object[key], key, object) === false) {
883 | break;
884 | }
885 | }
886 | }
887 | // else iterate only enumerable properties
888 | else {
889 | for (key in object) {
890 | // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
891 | // (if the prototype or a property on the prototype has been set)
892 | // incorrectly set a function's `prototype` property [[Enumerable]] value
893 | // to `true`. Because of this we standardize on skipping the `prototype`
894 | // property of functions regardless of their [[Enumerable]] value.
895 | if ((done =
896 | !(skipProto && key == 'prototype') &&
897 | !(skipSeen && (hasKey(seen, key) || !(seen[key] = true))) &&
898 | (!ownFlag || ownFlag && hasKey(object, key)) &&
899 | callback(object[key], key, object) === false)) {
900 | break;
901 | }
902 | }
903 | // in IE < 9 strings don't support accessing characters by index
904 | if (!done && (forArgs && isArguments(object) ||
905 | ((noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String') &&
906 | (iteratee = noCharByIndex ? object.split('') : object)))) {
907 | while (++index < length) {
908 | if ((done =
909 | callback(iteratee[index], String(index), object) === false)) {
910 | break;
911 | }
912 | }
913 | }
914 | if (!done && forShadowed) {
915 | // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing
916 | // property and the `constructor` property of a prototype defaults to
917 | // non-enumerable, we manually skip the `constructor` property when we
918 | // think we are iterating over a `prototype` object.
919 | ctor = object.constructor;
920 | skipCtor = ctor && ctor.prototype && ctor.prototype.constructor === ctor;
921 | for (index = 0; index < 7; index++) {
922 | key = shadowed[index];
923 | if (!(skipCtor && key == 'constructor') &&
924 | hasKey(object, key) &&
925 | callback(object[key], key, object) === false) {
926 | break;
927 | }
928 | }
929 | }
930 | }
931 | return result;
932 | };
933 | return forProps.apply(null, arguments);
934 | }
935 |
936 | /**
937 | * Gets the name of the first argument from a function's source.
938 | *
939 | * @private
940 | * @param {Function} fn The function.
941 | * @returns {String} The argument name.
942 | */
943 | function getFirstArgument(fn) {
944 | return (!hasKey(fn, 'toString') &&
945 | (/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || '';
946 | }
947 |
948 | /**
949 | * Computes the arithmetic mean of a sample.
950 | *
951 | * @private
952 | * @param {Array} sample The sample.
953 | * @returns {Number} The mean.
954 | */
955 | function getMean(sample) {
956 | return reduce(sample, function(sum, x) {
957 | return sum + x;
958 | }) / sample.length || 0;
959 | }
960 |
961 | /**
962 | * Gets the source code of a function.
963 | *
964 | * @private
965 | * @param {Function} fn The function.
966 | * @param {String} altSource A string used when a function's source code is unretrievable.
967 | * @returns {String} The function's source code.
968 | */
969 | function getSource(fn, altSource) {
970 | var result = altSource;
971 | if (isStringable(fn)) {
972 | result = String(fn);
973 | } else if (support.decompilation) {
974 | // escape the `{` for Firefox 1
975 | result = (/^[^{]+\{([\s\S]*)}\s*$/.exec(fn) || 0)[1];
976 | }
977 | // trim string
978 | result = (result || '').replace(/^\s+|\s+$/g, '');
979 |
980 | // detect strings containing only the "use strict" directive
981 | return /^(?:\/\*+[\w|\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result)
982 | ? ''
983 | : result;
984 | }
985 |
986 | /**
987 | * Checks if a value is an `arguments` object.
988 | *
989 | * @private
990 | * @param {Mixed} value The value to check.
991 | * @returns {Boolean} Returns `true` if the value is an `arguments` object, else `false`.
992 | */
993 | function isArguments() {
994 | // lazy define
995 | isArguments = function(value) {
996 | return toString.call(value) == '[object Arguments]';
997 | };
998 | if (noArgumentsClass) {
999 | isArguments = function(value) {
1000 | return hasKey(value, 'callee') &&
1001 | !(propertyIsEnumerable && propertyIsEnumerable.call(value, 'callee'));
1002 | };
1003 | }
1004 | return isArguments(arguments[0]);
1005 | }
1006 |
1007 | /**
1008 | * Checks if an object is of the specified class.
1009 | *
1010 | * @private
1011 | * @param {Mixed} value The value to check.
1012 | * @param {String} name The name of the class.
1013 | * @returns {Boolean} Returns `true` if the value is of the specified class, else `false`.
1014 | */
1015 | function isClassOf(value, name) {
1016 | return value != null && toString.call(value) == '[object ' + name + ']';
1017 | }
1018 |
1019 | /**
1020 | * Host objects can return type values that are different from their actual
1021 | * data type. The objects we are concerned with usually return non-primitive
1022 | * types of object, function, or unknown.
1023 | *
1024 | * @private
1025 | * @param {Mixed} object The owner of the property.
1026 | * @param {String} property The property to check.
1027 | * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`.
1028 | */
1029 | function isHostType(object, property) {
1030 | var type = object != null ? typeof object[property] : 'number';
1031 | return !/^(?:boolean|number|string|undefined)$/.test(type) &&
1032 | (type == 'object' ? !!object[property] : true);
1033 | }
1034 |
1035 | /**
1036 | * Checks if a given `value` is an object created by the `Object` constructor
1037 | * assuming objects created by the `Object` constructor have no inherited
1038 | * enumerable properties and that there are no `Object.prototype` extensions.
1039 | *
1040 | * @private
1041 | * @param {Mixed} value The value to check.
1042 | * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, else `false`.
1043 | */
1044 | function isPlainObject(value) {
1045 | // avoid non-objects and false positives for `arguments` objects in IE < 9
1046 | var result = false;
1047 | if (!(value && typeof value == 'object') || isArguments(value)) {
1048 | return result;
1049 | }
1050 | // IE < 9 presents DOM nodes as `Object` objects except they have `toString`
1051 | // methods that are `typeof` "string" and still can coerce nodes to strings.
1052 | // Also check that the constructor is `Object` (i.e. `Object instanceof Object`)
1053 | var ctor = value.constructor;
1054 | if ((support.nodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) &&
1055 | (!isClassOf(ctor, 'Function') || ctor instanceof ctor)) {
1056 | // In most environments an object's own properties are iterated before
1057 | // its inherited properties. If the last iterated property is an object's
1058 | // own property then there are no inherited enumerable properties.
1059 | if (support.iteratesOwnFirst) {
1060 | forProps(value, function(subValue, subKey) {
1061 | result = subKey;
1062 | });
1063 | return result === false || hasKey(value, result);
1064 | }
1065 | // IE < 9 iterates inherited properties before own properties. If the first
1066 | // iterated property is an object's own property then there are no inherited
1067 | // enumerable properties.
1068 | forProps(value, function(subValue, subKey) {
1069 | result = !hasKey(value, subKey);
1070 | return false;
1071 | });
1072 | return result === false;
1073 | }
1074 | return result;
1075 | }
1076 |
1077 | /**
1078 | * Checks if a value can be safely coerced to a string.
1079 | *
1080 | * @private
1081 | * @param {Mixed} value The value to check.
1082 | * @returns {Boolean} Returns `true` if the value can be coerced, else `false`.
1083 | */
1084 | function isStringable(value) {
1085 | return hasKey(value, 'toString') || isClassOf(value, 'String');
1086 | }
1087 |
1088 | /**
1089 | * Wraps a function and passes `this` to the original function as the
1090 | * first argument.
1091 | *
1092 | * @private
1093 | * @param {Function} fn The function to be wrapped.
1094 | * @returns {Function} The new function.
1095 | */
1096 | function methodize(fn) {
1097 | return function() {
1098 | var args = [this];
1099 | args.push.apply(args, arguments);
1100 | return fn.apply(null, args);
1101 | };
1102 | }
1103 |
1104 | /**
1105 | * A no-operation function.
1106 | *
1107 | * @private
1108 | */
1109 | function noop() {
1110 | // no operation performed
1111 | }
1112 |
1113 | /**
1114 | * A wrapper around require() to suppress `module missing` errors.
1115 | *
1116 | * @private
1117 | * @param {String} id The module id.
1118 | * @returns {Mixed} The exported module or `null`.
1119 | */
1120 | function req(id) {
1121 | try {
1122 | var result = freeExports && freeRequire(id);
1123 | } catch(e) { }
1124 | return result || null;
1125 | }
1126 |
1127 | /**
1128 | * Runs a snippet of JavaScript via script injection.
1129 | *
1130 | * @private
1131 | * @param {String} code The code to run.
1132 | */
1133 | function runScript(code) {
1134 | var anchor = freeDefine ? define.amd : Benchmark,
1135 | script = doc.createElement('script'),
1136 | sibling = doc.getElementsByTagName('script')[0],
1137 | parent = sibling.parentNode,
1138 | prop = uid + 'runScript',
1139 | prefix = '(' + (freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '||function(){})();';
1140 |
1141 | // Firefox 2.0.0.2 cannot use script injection as intended because it executes
1142 | // asynchronously, but that's OK because script injection is only used to avoid
1143 | // the previously commented JaegerMonkey bug.
1144 | try {
1145 | // remove the inserted script *before* running the code to avoid differences
1146 | // in the expected script element count/order of the document.
1147 | script.appendChild(doc.createTextNode(prefix + code));
1148 | anchor[prop] = function() { destroyElement(script); };
1149 | } catch(e) {
1150 | parent = parent.cloneNode(false);
1151 | sibling = null;
1152 | script.text = code;
1153 | }
1154 | parent.insertBefore(script, sibling);
1155 | delete anchor[prop];
1156 | }
1157 |
1158 | /**
1159 | * A helper function for setting options/event handlers.
1160 | *
1161 | * @private
1162 | * @param {Object} bench The benchmark instance.
1163 | * @param {Object} [options={}] Options object.
1164 | */
1165 | function setOptions(bench, options) {
1166 | options = extend({}, bench.constructor.options, options);
1167 | bench.options = forOwn(options, function(value, key) {
1168 | if (value != null) {
1169 | // add event listeners
1170 | if (/^on[A-Z]/.test(key)) {
1171 | forEach(key.split(' '), function(key) {
1172 | bench.on(key.slice(2).toLowerCase(), value);
1173 | });
1174 | } else if (!hasKey(bench, key)) {
1175 | bench[key] = deepClone(value);
1176 | }
1177 | }
1178 | });
1179 | }
1180 |
1181 | /*--------------------------------------------------------------------------*/
1182 |
1183 | /**
1184 | * Handles cycling/completing the deferred benchmark.
1185 | *
1186 | * @memberOf Benchmark.Deferred
1187 | */
1188 | function resolve() {
1189 | var me = this,
1190 | clone = me.benchmark,
1191 | bench = clone._original;
1192 |
1193 | if (bench.aborted) {
1194 | // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete
1195 | me.teardown();
1196 | clone.running = false;
1197 | cycle(me);
1198 | }
1199 | else if (++me.cycles < clone.count) {
1200 | // continue the test loop
1201 | if (support.timeout) {
1202 | // use setTimeout to avoid a call stack overflow if called recursively
1203 | setTimeout(function() { clone.compiled.call(me, timer); }, 0);
1204 | } else {
1205 | clone.compiled.call(me, timer);
1206 | }
1207 | }
1208 | else {
1209 | timer.stop(me);
1210 | me.teardown();
1211 | delay(clone, function() { cycle(me); });
1212 | }
1213 | }
1214 |
1215 | /*--------------------------------------------------------------------------*/
1216 |
1217 | /**
1218 | * A deep clone utility.
1219 | *
1220 | * @static
1221 | * @memberOf Benchmark
1222 | * @param {Mixed} value The value to clone.
1223 | * @returns {Mixed} The cloned value.
1224 | */
1225 | function deepClone(value) {
1226 | var accessor,
1227 | circular,
1228 | clone,
1229 | ctor,
1230 | descriptor,
1231 | extensible,
1232 | key,
1233 | length,
1234 | markerKey,
1235 | parent,
1236 | result,
1237 | source,
1238 | subIndex,
1239 | data = { 'value': value },
1240 | index = 0,
1241 | marked = [],
1242 | queue = { 'length': 0 },
1243 | unmarked = [];
1244 |
1245 | /**
1246 | * An easily detectable decorator for cloned values.
1247 | */
1248 | function Marker(object) {
1249 | this.raw = object;
1250 | }
1251 |
1252 | /**
1253 | * The callback used by `forProps()`.
1254 | */
1255 | function forPropsCallback(subValue, subKey) {
1256 | // exit early to avoid cloning the marker
1257 | if (subValue && subValue.constructor == Marker) {
1258 | return;
1259 | }
1260 | // add objects to the queue
1261 | if (subValue === Object(subValue)) {
1262 | queue[queue.length++] = { 'key': subKey, 'parent': clone, 'source': value };
1263 | }
1264 | // assign non-objects
1265 | else {
1266 | try {
1267 | // will throw an error in strict mode if the property is read-only
1268 | clone[subKey] = subValue;
1269 | } catch(e) { }
1270 | }
1271 | }
1272 |
1273 | /**
1274 | * Gets an available marker key for the given object.
1275 | */
1276 | function getMarkerKey(object) {
1277 | // avoid collisions with existing keys
1278 | var result = uid;
1279 | while (object[result] && object[result].constructor != Marker) {
1280 | result += 1;
1281 | }
1282 | return result;
1283 | }
1284 |
1285 | do {
1286 | key = data.key;
1287 | parent = data.parent;
1288 | source = data.source;
1289 | clone = value = source ? source[key] : data.value;
1290 | accessor = circular = descriptor = false;
1291 |
1292 | // create a basic clone to filter out functions, DOM elements, and
1293 | // other non `Object` objects
1294 | if (value === Object(value)) {
1295 | // use custom deep clone function if available
1296 | if (isClassOf(value.deepClone, 'Function')) {
1297 | clone = value.deepClone();
1298 | } else {
1299 | ctor = value.constructor;
1300 | switch (toString.call(value)) {
1301 | case '[object Array]':
1302 | clone = new ctor(value.length);
1303 | break;
1304 |
1305 | case '[object Boolean]':
1306 | clone = new ctor(value == true);
1307 | break;
1308 |
1309 | case '[object Date]':
1310 | clone = new ctor(+value);
1311 | break;
1312 |
1313 | case '[object Object]':
1314 | isPlainObject(value) && (clone = {});
1315 | break;
1316 |
1317 | case '[object Number]':
1318 | case '[object String]':
1319 | clone = new ctor(value);
1320 | break;
1321 |
1322 | case '[object RegExp]':
1323 | clone = ctor(value.source,
1324 | (value.global ? 'g' : '') +
1325 | (value.ignoreCase ? 'i' : '') +
1326 | (value.multiline ? 'm' : ''));
1327 | }
1328 | }
1329 | // continue clone if `value` doesn't have an accessor descriptor
1330 | // http://es5.github.com/#x8.10.1
1331 | if (clone && clone != value &&
1332 | !(descriptor = source && support.descriptors && getDescriptor(source, key),
1333 | accessor = descriptor && (descriptor.get || descriptor.set))) {
1334 | // use an existing clone (circular reference)
1335 | if ((extensible = isExtensible(value))) {
1336 | markerKey = getMarkerKey(value);
1337 | if (value[markerKey]) {
1338 | circular = clone = value[markerKey].raw;
1339 | }
1340 | } else {
1341 | // for frozen/sealed objects
1342 | for (subIndex = 0, length = unmarked.length; subIndex < length; subIndex++) {
1343 | data = unmarked[subIndex];
1344 | if (data.object === value) {
1345 | circular = clone = data.clone;
1346 | break;
1347 | }
1348 | }
1349 | }
1350 | if (!circular) {
1351 | // mark object to allow quickly detecting circular references and tie it to its clone
1352 | if (extensible) {
1353 | value[markerKey] = new Marker(clone);
1354 | marked.push({ 'key': markerKey, 'object': value });
1355 | } else {
1356 | // for frozen/sealed objects
1357 | unmarked.push({ 'clone': clone, 'object': value });
1358 | }
1359 | // iterate over object properties
1360 | forProps(value, forPropsCallback, { 'which': 'all' });
1361 | }
1362 | }
1363 | }
1364 | if (parent) {
1365 | // for custom property descriptors
1366 | if (accessor || (descriptor && !(descriptor.configurable && descriptor.enumerable && descriptor.writable))) {
1367 | if ('value' in descriptor) {
1368 | descriptor.value = clone;
1369 | }
1370 | setDescriptor(parent, key, descriptor);
1371 | }
1372 | // for default property descriptors
1373 | else {
1374 | parent[key] = clone;
1375 | }
1376 | } else {
1377 | result = clone;
1378 | }
1379 | } while ((data = queue[index++]));
1380 |
1381 | // remove markers
1382 | for (index = 0, length = marked.length; index < length; index++) {
1383 | data = marked[index];
1384 | delete data.object[data.key];
1385 | }
1386 | return result;
1387 | }
1388 |
1389 | /**
1390 | * An iteration utility for arrays and objects.
1391 | * Callbacks may terminate the loop by explicitly returning `false`.
1392 | *
1393 | * @static
1394 | * @memberOf Benchmark
1395 | * @param {Array|Object} object The object to iterate over.
1396 | * @param {Function} callback The function called per iteration.
1397 | * @param {Mixed} thisArg The `this` binding for the callback.
1398 | * @returns {Array|Object} Returns the object iterated over.
1399 | */
1400 | function each(object, callback, thisArg) {
1401 | var result = object;
1402 | object = Object(object);
1403 |
1404 | var fn = callback,
1405 | index = -1,
1406 | length = object.length,
1407 | isSnapshot = !!(object.snapshotItem && (length = object.snapshotLength)),
1408 | isSplittable = (noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String'),
1409 | isConvertable = isSnapshot || isSplittable || 'item' in object,
1410 | origObject = object;
1411 |
1412 | // in Opera < 10.5 `hasKey(object, 'length')` returns `false` for NodeLists
1413 | if (length === length >>> 0) {
1414 | if (isConvertable) {
1415 | // the third argument of the callback is the original non-array object
1416 | callback = function(value, index) {
1417 | return fn.call(this, value, index, origObject);
1418 | };
1419 | // in IE < 9 strings don't support accessing characters by index
1420 | if (isSplittable) {
1421 | object = object.split('');
1422 | } else {
1423 | object = [];
1424 | while (++index < length) {
1425 | // in Safari 2 `index in object` is always `false` for NodeLists
1426 | object[index] = isSnapshot ? result.snapshotItem(index) : result[index];
1427 | }
1428 | }
1429 | }
1430 | forEach(object, callback, thisArg);
1431 | } else {
1432 | forOwn(object, callback, thisArg);
1433 | }
1434 | return result;
1435 | }
1436 |
1437 | /**
1438 | * Copies enumerable properties from the source(s) object to the destination object.
1439 | *
1440 | * @static
1441 | * @memberOf Benchmark
1442 | * @param {Object} destination The destination object.
1443 | * @param {Object} [source={}] The source object.
1444 | * @returns {Object} The destination object.
1445 | */
1446 | function extend(destination, source) {
1447 | // Chrome < 14 incorrectly sets `destination` to `undefined` when we `delete arguments[0]`
1448 | // http://code.google.com/p/v8/issues/detail?id=839
1449 | var result = destination;
1450 | delete arguments[0];
1451 |
1452 | forEach(arguments, function(source) {
1453 | forProps(source, function(value, key) {
1454 | result[key] = value;
1455 | });
1456 | });
1457 | return result;
1458 | }
1459 |
1460 | /**
1461 | * A generic `Array#filter` like method.
1462 | *
1463 | * @static
1464 | * @memberOf Benchmark
1465 | * @param {Array} array The array to iterate over.
1466 | * @param {Function|String} callback The function/alias called per iteration.
1467 | * @param {Mixed} thisArg The `this` binding for the callback.
1468 | * @returns {Array} A new array of values that passed callback filter.
1469 | * @example
1470 | *
1471 | * // get odd numbers
1472 | * Benchmark.filter([1, 2, 3, 4, 5], function(n) {
1473 | * return n % 2;
1474 | * }); // -> [1, 3, 5];
1475 | *
1476 | * // get fastest benchmarks
1477 | * Benchmark.filter(benches, 'fastest');
1478 | *
1479 | * // get slowest benchmarks
1480 | * Benchmark.filter(benches, 'slowest');
1481 | *
1482 | * // get benchmarks that completed without erroring
1483 | * Benchmark.filter(benches, 'successful');
1484 | */
1485 | function filter(array, callback, thisArg) {
1486 | var result;
1487 |
1488 | if (callback == 'successful') {
1489 | // callback to exclude those that are errored, unrun, or have hz of Infinity
1490 | callback = function(bench) { return bench.cycles && isFinite(bench.hz); };
1491 | }
1492 | else if (callback == 'fastest' || callback == 'slowest') {
1493 | // get successful, sort by period + margin of error, and filter fastest/slowest
1494 | result = filter(array, 'successful').sort(function(a, b) {
1495 | a = a.stats; b = b.stats;
1496 | return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback == 'fastest' ? 1 : -1);
1497 | });
1498 | result = filter(result, function(bench) {
1499 | return result[0].compare(bench) == 0;
1500 | });
1501 | }
1502 | return result || reduce(array, function(result, value, index) {
1503 | return callback.call(thisArg, value, index, array) ? (result.push(value), result) : result;
1504 | }, []);
1505 | }
1506 |
1507 | /**
1508 | * A generic `Array#forEach` like method.
1509 | * Callbacks may terminate the loop by explicitly returning `false`.
1510 | *
1511 | * @static
1512 | * @memberOf Benchmark
1513 | * @param {Array} array The array to iterate over.
1514 | * @param {Function} callback The function called per iteration.
1515 | * @param {Mixed} thisArg The `this` binding for the callback.
1516 | * @returns {Array} Returns the array iterated over.
1517 | */
1518 | function forEach(array, callback, thisArg) {
1519 | var index = -1,
1520 | length = (array = Object(array)).length >>> 0;
1521 |
1522 | if (thisArg !== undefined) {
1523 | callback = bind(callback, thisArg);
1524 | }
1525 | while (++index < length) {
1526 | if (index in array &&
1527 | callback(array[index], index, array) === false) {
1528 | break;
1529 | }
1530 | }
1531 | return array;
1532 | }
1533 |
1534 | /**
1535 | * Iterates over an object's own properties, executing the `callback` for each.
1536 | * Callbacks may terminate the loop by explicitly returning `false`.
1537 | *
1538 | * @static
1539 | * @memberOf Benchmark
1540 | * @param {Object} object The object to iterate over.
1541 | * @param {Function} callback The function executed per own property.
1542 | * @param {Mixed} thisArg The `this` binding for the callback.
1543 | * @returns {Object} Returns the object iterated over.
1544 | */
1545 | function forOwn(object, callback, thisArg) {
1546 | return forProps(object, callback, { 'bind': thisArg, 'which': 'own' });
1547 | }
1548 |
1549 | /**
1550 | * Converts a number to a more readable comma-separated string representation.
1551 | *
1552 | * @static
1553 | * @memberOf Benchmark
1554 | * @param {Number} number The number to convert.
1555 | * @returns {String} The more readable string representation.
1556 | */
1557 | function formatNumber(number) {
1558 | number = String(number).split('.');
1559 | return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') +
1560 | (number[1] ? '.' + number[1] : '');
1561 | }
1562 |
1563 | /**
1564 | * Checks if an object has the specified key as a direct property.
1565 | *
1566 | * @static
1567 | * @memberOf Benchmark
1568 | * @param {Object} object The object to check.
1569 | * @param {String} key The key to check for.
1570 | * @returns {Boolean} Returns `true` if key is a direct property, else `false`.
1571 | */
1572 | function hasKey() {
1573 | // lazy define for worst case fallback (not as accurate)
1574 | hasKey = function(object, key) {
1575 | var parent = object != null && (object.constructor || Object).prototype;
1576 | return !!parent && key in Object(object) && !(key in parent && object[key] === parent[key]);
1577 | };
1578 | // for modern browsers
1579 | if (isClassOf(hasOwnProperty, 'Function')) {
1580 | hasKey = function(object, key) {
1581 | return object != null && hasOwnProperty.call(object, key);
1582 | };
1583 | }
1584 | // for Safari 2
1585 | else if ({}.__proto__ == Object.prototype) {
1586 | hasKey = function(object, key) {
1587 | var result = false;
1588 | if (object != null) {
1589 | object = Object(object);
1590 | object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0];
1591 | }
1592 | return result;
1593 | };
1594 | }
1595 | return hasKey.apply(this, arguments);
1596 | }
1597 |
1598 | /**
1599 | * A generic `Array#indexOf` like method.
1600 | *
1601 | * @static
1602 | * @memberOf Benchmark
1603 | * @param {Array} array The array to iterate over.
1604 | * @param {Mixed} value The value to search for.
1605 | * @param {Number} [fromIndex=0] The index to start searching from.
1606 | * @returns {Number} The index of the matched value or `-1`.
1607 | */
1608 | function indexOf(array, value, fromIndex) {
1609 | var index = toInteger(fromIndex),
1610 | length = (array = Object(array)).length >>> 0;
1611 |
1612 | index = (index < 0 ? max(0, length + index) : index) - 1;
1613 | while (++index < length) {
1614 | if (index in array && value === array[index]) {
1615 | return index;
1616 | }
1617 | }
1618 | return -1;
1619 | }
1620 |
1621 | /**
1622 | * Modify a string by replacing named tokens with matching object property values.
1623 | *
1624 | * @static
1625 | * @memberOf Benchmark
1626 | * @param {String} string The string to modify.
1627 | * @param {Object} object The template object.
1628 | * @returns {String} The modified string.
1629 | */
1630 | function interpolate(string, object) {
1631 | forOwn(object, function(value, key) {
1632 | // escape regexp special characters in `key`
1633 | string = string.replace(RegExp('#\\{' + key.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1') + '\\}', 'g'), value);
1634 | });
1635 | return string;
1636 | }
1637 |
1638 | /**
1639 | * Invokes a method on all items in an array.
1640 | *
1641 | * @static
1642 | * @memberOf Benchmark
1643 | * @param {Array} benches Array of benchmarks to iterate over.
1644 | * @param {String|Object} name The name of the method to invoke OR options object.
1645 | * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
1646 | * @returns {Array} A new array of values returned from each method invoked.
1647 | * @example
1648 | *
1649 | * // invoke `reset` on all benchmarks
1650 | * Benchmark.invoke(benches, 'reset');
1651 | *
1652 | * // invoke `emit` with arguments
1653 | * Benchmark.invoke(benches, 'emit', 'complete', listener);
1654 | *
1655 | * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks
1656 | * Benchmark.invoke(benches, {
1657 | *
1658 | * // invoke the `run` method
1659 | * 'name': 'run',
1660 | *
1661 | * // pass a single argument
1662 | * 'args': true,
1663 | *
1664 | * // treat as queue, removing benchmarks from front of `benches` until empty
1665 | * 'queued': true,
1666 | *
1667 | * // called before any benchmarks have been invoked.
1668 | * 'onStart': onStart,
1669 | *
1670 | * // called between invoking benchmarks
1671 | * 'onCycle': onCycle,
1672 | *
1673 | * // called after all benchmarks have been invoked.
1674 | * 'onComplete': onComplete
1675 | * });
1676 | */
1677 | function invoke(benches, name) {
1678 | var args,
1679 | bench,
1680 | queued,
1681 | index = -1,
1682 | eventProps = { 'currentTarget': benches },
1683 | options = { 'onStart': noop, 'onCycle': noop, 'onComplete': noop },
1684 | result = map(benches, function(bench) { return bench; });
1685 |
1686 | /**
1687 | * Invokes the method of the current object and if synchronous, fetches the next.
1688 | */
1689 | function execute() {
1690 | var listeners,
1691 | async = isAsync(bench);
1692 |
1693 | if (async) {
1694 | // use `getNext` as the first listener
1695 | bench.on('complete', getNext);
1696 | listeners = bench.events.complete;
1697 | listeners.splice(0, 0, listeners.pop());
1698 | }
1699 | // execute method
1700 | result[index] = isClassOf(bench && bench[name], 'Function') ? bench[name].apply(bench, args) : undefined;
1701 | // if synchronous return true until finished
1702 | return !async && getNext();
1703 | }
1704 |
1705 | /**
1706 | * Fetches the next bench or executes `onComplete` callback.
1707 | */
1708 | function getNext(event) {
1709 | var cycleEvent,
1710 | last = bench,
1711 | async = isAsync(last);
1712 |
1713 | if (async) {
1714 | last.off('complete', getNext);
1715 | last.emit('complete');
1716 | }
1717 | // emit "cycle" event
1718 | eventProps.type = 'cycle';
1719 | eventProps.target = last;
1720 | cycleEvent = Event(eventProps);
1721 | options.onCycle.call(benches, cycleEvent);
1722 |
1723 | // choose next benchmark if not exiting early
1724 | if (!cycleEvent.aborted && raiseIndex() !== false) {
1725 | bench = queued ? benches[0] : result[index];
1726 | if (isAsync(bench)) {
1727 | delay(bench, execute);
1728 | }
1729 | else if (async) {
1730 | // resume execution if previously asynchronous but now synchronous
1731 | while (execute()) { }
1732 | }
1733 | else {
1734 | // continue synchronous execution
1735 | return true;
1736 | }
1737 | } else {
1738 | // emit "complete" event
1739 | eventProps.type = 'complete';
1740 | options.onComplete.call(benches, Event(eventProps));
1741 | }
1742 | // When used as a listener `event.aborted = true` will cancel the rest of
1743 | // the "complete" listeners because they were already called above and when
1744 | // used as part of `getNext` the `return false` will exit the execution while-loop.
1745 | if (event) {
1746 | event.aborted = true;
1747 | } else {
1748 | return false;
1749 | }
1750 | }
1751 |
1752 | /**
1753 | * Checks if invoking `Benchmark#run` with asynchronous cycles.
1754 | */
1755 | function isAsync(object) {
1756 | // avoid using `instanceof` here because of IE memory leak issues with host objects
1757 | var async = args[0] && args[0].async;
1758 | return Object(object).constructor == Benchmark && name == 'run' &&
1759 | ((async == null ? object.options.async : async) && support.timeout || object.defer);
1760 | }
1761 |
1762 | /**
1763 | * Raises `index` to the next defined index or returns `false`.
1764 | */
1765 | function raiseIndex() {
1766 | var length = result.length;
1767 | if (queued) {
1768 | // if queued remove the previous bench and subsequent skipped non-entries
1769 | do {
1770 | ++index > 0 && shift.call(benches);
1771 | } while ((length = benches.length) && !('0' in benches));
1772 | }
1773 | else {
1774 | while (++index < length && !(index in result)) { }
1775 | }
1776 | // if we reached the last index then return `false`
1777 | return (queued ? length : index < length) ? index : (index = false);
1778 | }
1779 |
1780 | // juggle arguments
1781 | if (isClassOf(name, 'String')) {
1782 | // 2 arguments (array, name)
1783 | args = slice.call(arguments, 2);
1784 | } else {
1785 | // 2 arguments (array, options)
1786 | options = extend(options, name);
1787 | name = options.name;
1788 | args = isClassOf(args = 'args' in options ? options.args : [], 'Array') ? args : [args];
1789 | queued = options.queued;
1790 | }
1791 |
1792 | // start iterating over the array
1793 | if (raiseIndex() !== false) {
1794 | // emit "start" event
1795 | bench = result[index];
1796 | eventProps.type = 'start';
1797 | eventProps.target = bench;
1798 | options.onStart.call(benches, Event(eventProps));
1799 |
1800 | // end early if the suite was aborted in an "onStart" listener
1801 | if (benches.aborted && benches.constructor == Suite && name == 'run') {
1802 | // emit "cycle" event
1803 | eventProps.type = 'cycle';
1804 | options.onCycle.call(benches, Event(eventProps));
1805 | // emit "complete" event
1806 | eventProps.type = 'complete';
1807 | options.onComplete.call(benches, Event(eventProps));
1808 | }
1809 | // else start
1810 | else {
1811 | if (isAsync(bench)) {
1812 | delay(bench, execute);
1813 | } else {
1814 | while (execute()) { }
1815 | }
1816 | }
1817 | }
1818 | return result;
1819 | }
1820 |
1821 | /**
1822 | * Creates a string of joined array values or object key-value pairs.
1823 | *
1824 | * @static
1825 | * @memberOf Benchmark
1826 | * @param {Array|Object} object The object to operate on.
1827 | * @param {String} [separator1=','] The separator used between key-value pairs.
1828 | * @param {String} [separator2=': '] The separator used between keys and values.
1829 | * @returns {String} The joined result.
1830 | */
1831 | function join(object, separator1, separator2) {
1832 | var result = [],
1833 | length = (object = Object(object)).length,
1834 | arrayLike = length === length >>> 0;
1835 |
1836 | separator2 || (separator2 = ': ');
1837 | each(object, function(value, key) {
1838 | result.push(arrayLike ? value : key + separator2 + value);
1839 | });
1840 | return result.join(separator1 || ',');
1841 | }
1842 |
1843 | /**
1844 | * A generic `Array#map` like method.
1845 | *
1846 | * @static
1847 | * @memberOf Benchmark
1848 | * @param {Array} array The array to iterate over.
1849 | * @param {Function} callback The function called per iteration.
1850 | * @param {Mixed} thisArg The `this` binding for the callback.
1851 | * @returns {Array} A new array of values returned by the callback.
1852 | */
1853 | function map(array, callback, thisArg) {
1854 | return reduce(array, function(result, value, index) {
1855 | result[index] = callback.call(thisArg, value, index, array);
1856 | return result;
1857 | }, Array(Object(array).length >>> 0));
1858 | }
1859 |
1860 | /**
1861 | * Retrieves the value of a specified property from all items in an array.
1862 | *
1863 | * @static
1864 | * @memberOf Benchmark
1865 | * @param {Array} array The array to iterate over.
1866 | * @param {String} property The property to pluck.
1867 | * @returns {Array} A new array of property values.
1868 | */
1869 | function pluck(array, property) {
1870 | return map(array, function(object) {
1871 | return object == null ? undefined : object[property];
1872 | });
1873 | }
1874 |
1875 | /**
1876 | * A generic `Array#reduce` like method.
1877 | *
1878 | * @static
1879 | * @memberOf Benchmark
1880 | * @param {Array} array The array to iterate over.
1881 | * @param {Function} callback The function called per iteration.
1882 | * @param {Mixed} accumulator Initial value of the accumulator.
1883 | * @returns {Mixed} The accumulator.
1884 | */
1885 | function reduce(array, callback, accumulator) {
1886 | var noaccum = arguments.length < 3;
1887 | forEach(array, function(value, index) {
1888 | accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, array);
1889 | });
1890 | return accumulator;
1891 | }
1892 |
1893 | /*--------------------------------------------------------------------------*/
1894 |
1895 | /**
1896 | * Aborts all benchmarks in the suite.
1897 | *
1898 | * @name abort
1899 | * @memberOf Benchmark.Suite
1900 | * @returns {Object} The suite instance.
1901 | */
1902 | function abortSuite() {
1903 | var event,
1904 | me = this,
1905 | resetting = calledBy.resetSuite;
1906 |
1907 | if (me.running) {
1908 | event = Event('abort');
1909 | me.emit(event);
1910 | if (!event.cancelled || resetting) {
1911 | // avoid infinite recursion
1912 | calledBy.abortSuite = true;
1913 | me.reset();
1914 | delete calledBy.abortSuite;
1915 |
1916 | if (!resetting) {
1917 | me.aborted = true;
1918 | invoke(me, 'abort');
1919 | }
1920 | }
1921 | }
1922 | return me;
1923 | }
1924 |
1925 | /**
1926 | * Adds a test to the benchmark suite.
1927 | *
1928 | * @memberOf Benchmark.Suite
1929 | * @param {String} name A name to identify the benchmark.
1930 | * @param {Function|String} fn The test to benchmark.
1931 | * @param {Object} [options={}] Options object.
1932 | * @returns {Object} The benchmark instance.
1933 | * @example
1934 | *
1935 | * // basic usage
1936 | * suite.add(fn);
1937 | *
1938 | * // or using a name first
1939 | * suite.add('foo', fn);
1940 | *
1941 | * // or with options
1942 | * suite.add('foo', fn, {
1943 | * 'onCycle': onCycle,
1944 | * 'onComplete': onComplete
1945 | * });
1946 | *
1947 | * // or name and options
1948 | * suite.add('foo', {
1949 | * 'fn': fn,
1950 | * 'onCycle': onCycle,
1951 | * 'onComplete': onComplete
1952 | * });
1953 | *
1954 | * // or options only
1955 | * suite.add({
1956 | * 'name': 'foo',
1957 | * 'fn': fn,
1958 | * 'onCycle': onCycle,
1959 | * 'onComplete': onComplete
1960 | * });
1961 | */
1962 | function add(name, fn, options) {
1963 | var me = this,
1964 | bench = Benchmark(name, fn, options),
1965 | event = Event({ 'type': 'add', 'target': bench });
1966 |
1967 | if (me.emit(event), !event.cancelled) {
1968 | me.push(bench);
1969 | }
1970 | return me;
1971 | }
1972 |
1973 | /**
1974 | * Creates a new suite with cloned benchmarks.
1975 | *
1976 | * @name clone
1977 | * @memberOf Benchmark.Suite
1978 | * @param {Object} options Options object to overwrite cloned options.
1979 | * @returns {Object} The new suite instance.
1980 | */
1981 | function cloneSuite(options) {
1982 | var me = this,
1983 | result = new me.constructor(extend({}, me.options, options));
1984 |
1985 | // copy own properties
1986 | forOwn(me, function(value, key) {
1987 | if (!hasKey(result, key)) {
1988 | result[key] = value && isClassOf(value.clone, 'Function')
1989 | ? value.clone()
1990 | : deepClone(value);
1991 | }
1992 | });
1993 | return result;
1994 | }
1995 |
1996 | /**
1997 | * An `Array#filter` like method.
1998 | *
1999 | * @name filter
2000 | * @memberOf Benchmark.Suite
2001 | * @param {Function|String} callback The function/alias called per iteration.
2002 | * @returns {Object} A new suite of benchmarks that passed callback filter.
2003 | */
2004 | function filterSuite(callback) {
2005 | var me = this,
2006 | result = new me.constructor;
2007 |
2008 | result.push.apply(result, filter(me, callback));
2009 | return result;
2010 | }
2011 |
2012 | /**
2013 | * Resets all benchmarks in the suite.
2014 | *
2015 | * @name reset
2016 | * @memberOf Benchmark.Suite
2017 | * @returns {Object} The suite instance.
2018 | */
2019 | function resetSuite() {
2020 | var event,
2021 | me = this,
2022 | aborting = calledBy.abortSuite;
2023 |
2024 | if (me.running && !aborting) {
2025 | // no worries, `resetSuite()` is called within `abortSuite()`
2026 | calledBy.resetSuite = true;
2027 | me.abort();
2028 | delete calledBy.resetSuite;
2029 | }
2030 | // reset if the state has changed
2031 | else if ((me.aborted || me.running) &&
2032 | (me.emit(event = Event('reset')), !event.cancelled)) {
2033 | me.running = false;
2034 | if (!aborting) {
2035 | invoke(me, 'reset');
2036 | }
2037 | }
2038 | return me;
2039 | }
2040 |
2041 | /**
2042 | * Runs the suite.
2043 | *
2044 | * @name run
2045 | * @memberOf Benchmark.Suite
2046 | * @param {Object} [options={}] Options object.
2047 | * @returns {Object} The suite instance.
2048 | * @example
2049 | *
2050 | * // basic usage
2051 | * suite.run();
2052 | *
2053 | * // or with options
2054 | * suite.run({ 'async': true, 'queued': true });
2055 | */
2056 | function runSuite(options) {
2057 | var me = this;
2058 |
2059 | me.reset();
2060 | me.running = true;
2061 | options || (options = {});
2062 |
2063 | invoke(me, {
2064 | 'name': 'run',
2065 | 'args': options,
2066 | 'queued': options.queued,
2067 | 'onStart': function(event) {
2068 | me.emit(event);
2069 | },
2070 | 'onCycle': function(event) {
2071 | var bench = event.target;
2072 | if (bench.error) {
2073 | me.emit({ 'type': 'error', 'target': bench });
2074 | }
2075 | me.emit(event);
2076 | event.aborted = me.aborted;
2077 | },
2078 | 'onComplete': function(event) {
2079 | me.running = false;
2080 | me.emit(event);
2081 | }
2082 | });
2083 | return me;
2084 | }
2085 |
2086 | /*--------------------------------------------------------------------------*/
2087 |
2088 | /**
2089 | * Executes all registered listeners of the specified event type.
2090 | *
2091 | * @memberOf Benchmark, Benchmark.Suite
2092 | * @param {String|Object} type The event type or object.
2093 | * @returns {Mixed} Returns the return value of the last listener executed.
2094 | */
2095 | function emit(type) {
2096 | var listeners,
2097 | me = this,
2098 | event = Event(type),
2099 | events = me.events,
2100 | args = (arguments[0] = event, arguments);
2101 |
2102 | event.currentTarget || (event.currentTarget = me);
2103 | event.target || (event.target = me);
2104 | delete event.result;
2105 |
2106 | if (events && (listeners = hasKey(events, event.type) && events[event.type])) {
2107 | forEach(listeners.slice(), function(listener) {
2108 | if ((event.result = listener.apply(me, args)) === false) {
2109 | event.cancelled = true;
2110 | }
2111 | return !event.aborted;
2112 | });
2113 | }
2114 | return event.result;
2115 | }
2116 |
2117 | /**
2118 | * Returns an array of event listeners for a given type that can be manipulated
2119 | * to add or remove listeners.
2120 | *
2121 | * @memberOf Benchmark, Benchmark.Suite
2122 | * @param {String} type The event type.
2123 | * @returns {Array} The listeners array.
2124 | */
2125 | function listeners(type) {
2126 | var me = this,
2127 | events = me.events || (me.events = {});
2128 |
2129 | return hasKey(events, type) ? events[type] : (events[type] = []);
2130 | }
2131 |
2132 | /**
2133 | * Unregisters a listener for the specified event type(s),
2134 | * or unregisters all listeners for the specified event type(s),
2135 | * or unregisters all listeners for all event types.
2136 | *
2137 | * @memberOf Benchmark, Benchmark.Suite
2138 | * @param {String} [type] The event type.
2139 | * @param {Function} [listener] The function to unregister.
2140 | * @returns {Object} The benchmark instance.
2141 | * @example
2142 | *
2143 | * // unregister a listener for an event type
2144 | * bench.off('cycle', listener);
2145 | *
2146 | * // unregister a listener for multiple event types
2147 | * bench.off('start cycle', listener);
2148 | *
2149 | * // unregister all listeners for an event type
2150 | * bench.off('cycle');
2151 | *
2152 | * // unregister all listeners for multiple event types
2153 | * bench.off('start cycle complete');
2154 | *
2155 | * // unregister all listeners for all event types
2156 | * bench.off();
2157 | */
2158 | function off(type, listener) {
2159 | var me = this,
2160 | events = me.events;
2161 |
2162 | events && each(type ? type.split(' ') : events, function(listeners, type) {
2163 | var index;
2164 | if (typeof listeners == 'string') {
2165 | type = listeners;
2166 | listeners = hasKey(events, type) && events[type];
2167 | }
2168 | if (listeners) {
2169 | if (listener) {
2170 | index = indexOf(listeners, listener);
2171 | if (index > -1) {
2172 | listeners.splice(index, 1);
2173 | }
2174 | } else {
2175 | listeners.length = 0;
2176 | }
2177 | }
2178 | });
2179 | return me;
2180 | }
2181 |
2182 | /**
2183 | * Registers a listener for the specified event type(s).
2184 | *
2185 | * @memberOf Benchmark, Benchmark.Suite
2186 | * @param {String} type The event type.
2187 | * @param {Function} listener The function to register.
2188 | * @returns {Object} The benchmark instance.
2189 | * @example
2190 | *
2191 | * // register a listener for an event type
2192 | * bench.on('cycle', listener);
2193 | *
2194 | * // register a listener for multiple event types
2195 | * bench.on('start cycle', listener);
2196 | */
2197 | function on(type, listener) {
2198 | var me = this,
2199 | events = me.events || (me.events = {});
2200 |
2201 | forEach(type.split(' '), function(type) {
2202 | (hasKey(events, type)
2203 | ? events[type]
2204 | : (events[type] = [])
2205 | ).push(listener);
2206 | });
2207 | return me;
2208 | }
2209 |
2210 | /*--------------------------------------------------------------------------*/
2211 |
2212 | /**
2213 | * Aborts the benchmark without recording times.
2214 | *
2215 | * @memberOf Benchmark
2216 | * @returns {Object} The benchmark instance.
2217 | */
2218 | function abort() {
2219 | var event,
2220 | me = this,
2221 | resetting = calledBy.reset;
2222 |
2223 | if (me.running) {
2224 | event = Event('abort');
2225 | me.emit(event);
2226 | if (!event.cancelled || resetting) {
2227 | // avoid infinite recursion
2228 | calledBy.abort = true;
2229 | me.reset();
2230 | delete calledBy.abort;
2231 |
2232 | if (support.timeout) {
2233 | clearTimeout(me._timerId);
2234 | delete me._timerId;
2235 | }
2236 | if (!resetting) {
2237 | me.aborted = true;
2238 | me.running = false;
2239 | }
2240 | }
2241 | }
2242 | return me;
2243 | }
2244 |
2245 | /**
2246 | * Creates a new benchmark using the same test and options.
2247 | *
2248 | * @memberOf Benchmark
2249 | * @param {Object} options Options object to overwrite cloned options.
2250 | * @returns {Object} The new benchmark instance.
2251 | * @example
2252 | *
2253 | * var bizarro = bench.clone({
2254 | * 'name': 'doppelganger'
2255 | * });
2256 | */
2257 | function clone(options) {
2258 | var me = this,
2259 | result = new me.constructor(extend({}, me, options));
2260 |
2261 | // correct the `options` object
2262 | result.options = extend({}, me.options, options);
2263 |
2264 | // copy own custom properties
2265 | forOwn(me, function(value, key) {
2266 | if (!hasKey(result, key)) {
2267 | result[key] = deepClone(value);
2268 | }
2269 | });
2270 | return result;
2271 | }
2272 |
2273 | /**
2274 | * Determines if a benchmark is faster than another.
2275 | *
2276 | * @memberOf Benchmark
2277 | * @param {Object} other The benchmark to compare.
2278 | * @returns {Number} Returns `-1` if slower, `1` if faster, and `0` if indeterminate.
2279 | */
2280 | function compare(other) {
2281 | var critical,
2282 | zStat,
2283 | me = this,
2284 | sample1 = me.stats.sample,
2285 | sample2 = other.stats.sample,
2286 | size1 = sample1.length,
2287 | size2 = sample2.length,
2288 | maxSize = max(size1, size2),
2289 | minSize = min(size1, size2),
2290 | u1 = getU(sample1, sample2),
2291 | u2 = getU(sample2, sample1),
2292 | u = min(u1, u2);
2293 |
2294 | function getScore(xA, sampleB) {
2295 | return reduce(sampleB, function(total, xB) {
2296 | return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5);
2297 | }, 0);
2298 | }
2299 |
2300 | function getU(sampleA, sampleB) {
2301 | return reduce(sampleA, function(total, xA) {
2302 | return total + getScore(xA, sampleB);
2303 | }, 0);
2304 | }
2305 |
2306 | function getZ(u) {
2307 | return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12);
2308 | }
2309 |
2310 | // exit early if comparing the same benchmark
2311 | if (me == other) {
2312 | return 0;
2313 | }
2314 | // reject the null hyphothesis the two samples come from the
2315 | // same population (i.e. have the same median) if...
2316 | if (size1 + size2 > 30) {
2317 | // ...the z-stat is greater than 1.96 or less than -1.96
2318 | // http://www.statisticslectures.com/topics/mannwhitneyu/
2319 | zStat = getZ(u);
2320 | return abs(zStat) > 1.96 ? (zStat > 0 ? -1 : 1) : 0;
2321 | }
2322 | // ...the U value is less than or equal the critical U value
2323 | // http://www.geoib.com/mann-whitney-u-test.html
2324 | critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3];
2325 | return u <= critical ? (u == u1 ? 1 : -1) : 0;
2326 | }
2327 |
2328 | /**
2329 | * Reset properties and abort if running.
2330 | *
2331 | * @memberOf Benchmark
2332 | * @returns {Object} The benchmark instance.
2333 | */
2334 | function reset() {
2335 | var data,
2336 | event,
2337 | me = this,
2338 | index = 0,
2339 | changes = { 'length': 0 },
2340 | queue = { 'length': 0 };
2341 |
2342 | if (me.running && !calledBy.abort) {
2343 | // no worries, `reset()` is called within `abort()`
2344 | calledBy.reset = true;
2345 | me.abort();
2346 | delete calledBy.reset;
2347 | }
2348 | else {
2349 | // a non-recursive solution to check if properties have changed
2350 | // http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4
2351 | data = { 'destination': me, 'source': extend({}, me.constructor.prototype, me.options) };
2352 | do {
2353 | forOwn(data.source, function(value, key) {
2354 | var changed,
2355 | destination = data.destination,
2356 | currValue = destination[key];
2357 |
2358 | if (value && typeof value == 'object') {
2359 | if (isClassOf(value, 'Array')) {
2360 | // check if an array value has changed to a non-array value
2361 | if (!isClassOf(currValue, 'Array')) {
2362 | changed = currValue = [];
2363 | }
2364 | // or has changed its length
2365 | if (currValue.length != value.length) {
2366 | changed = currValue = currValue.slice(0, value.length);
2367 | currValue.length = value.length;
2368 | }
2369 | }
2370 | // check if an object has changed to a non-object value
2371 | else if (!currValue || typeof currValue != 'object') {
2372 | changed = currValue = {};
2373 | }
2374 | // register a changed object
2375 | if (changed) {
2376 | changes[changes.length++] = { 'destination': destination, 'key': key, 'value': currValue };
2377 | }
2378 | queue[queue.length++] = { 'destination': currValue, 'source': value };
2379 | }
2380 | // register a changed primitive
2381 | else if (value !== currValue && !(value == null || isClassOf(value, 'Function'))) {
2382 | changes[changes.length++] = { 'destination': destination, 'key': key, 'value': value };
2383 | }
2384 | });
2385 | }
2386 | while ((data = queue[index++]));
2387 |
2388 | // if changed emit the `reset` event and if it isn't cancelled reset the benchmark
2389 | if (changes.length && (me.emit(event = Event('reset')), !event.cancelled)) {
2390 | forEach(changes, function(data) {
2391 | data.destination[data.key] = data.value;
2392 | });
2393 | }
2394 | }
2395 | return me;
2396 | }
2397 |
2398 | /**
2399 | * Displays relevant benchmark information when coerced to a string.
2400 | *
2401 | * @name toString
2402 | * @memberOf Benchmark
2403 | * @returns {String} A string representation of the benchmark instance.
2404 | */
2405 | function toStringBench() {
2406 | var me = this,
2407 | error = me.error,
2408 | hz = me.hz,
2409 | id = me.id,
2410 | stats = me.stats,
2411 | size = stats.sample.length,
2412 | pm = support.java ? '+/-' : '\xb1',
2413 | result = me.name || (isNaN(id) ? id : '');
2414 |
2415 | if (error) {
2416 | result += ': ' + join(error);
2417 | } else {
2418 | result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm +
2419 | stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)';
2420 | }
2421 | return result;
2422 | }
2423 |
2424 | /*--------------------------------------------------------------------------*/
2425 |
2426 | /**
2427 | * Clocks the time taken to execute a test per cycle (secs).
2428 | *
2429 | * @private
2430 | * @param {Object} bench The benchmark instance.
2431 | * @returns {Number} The time taken.
2432 | */
2433 | function clock() {
2434 | var applet,
2435 | options = Benchmark.options,
2436 | template = { 'begin': 's$=new n$', 'end': 'r$=(new n$-s$)/1e3', 'uid': uid },
2437 | timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }];
2438 |
2439 | // lazy define for hi-res timers
2440 | clock = function(clone) {
2441 | var deferred;
2442 | if (clone instanceof Deferred) {
2443 | deferred = clone;
2444 | clone = deferred.benchmark;
2445 | }
2446 |
2447 | var bench = clone._original,
2448 | fn = bench.fn,
2449 | fnArg = deferred ? getFirstArgument(fn) || 'deferred' : '',
2450 | stringable = isStringable(fn);
2451 |
2452 | var source = {
2453 | 'setup': getSource(bench.setup, preprocess('m$.setup()')),
2454 | 'fn': getSource(fn, preprocess('m$.fn(' + fnArg + ')')),
2455 | 'fnArg': fnArg,
2456 | 'teardown': getSource(bench.teardown, preprocess('m$.teardown()'))
2457 | };
2458 |
2459 | var count = bench.count = clone.count,
2460 | decompilable = support.decompilation || stringable,
2461 | id = bench.id,
2462 | isEmpty = !(source.fn || stringable),
2463 | name = bench.name || (typeof id == 'number' ? '' : id),
2464 | ns = timer.ns,
2465 | result = 0;
2466 |
2467 | // init `minTime` if needed
2468 | clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime);
2469 |
2470 | // repair nanosecond timer
2471 | // (some Chrome builds erase the `ns` variable after millions of executions)
2472 | if (applet) {
2473 | try {
2474 | ns.nanoTime();
2475 | } catch(e) {
2476 | // use non-element to avoid issues with libs that augment them
2477 | ns = timer.ns = new applet.Packages.nano;
2478 | }
2479 | }
2480 |
2481 | // Compile in setup/teardown functions and the test loop.
2482 | // Create a new compiled test, instead of using the cached `bench.compiled`,
2483 | // to avoid potential engine optimizations enabled over the life of the test.
2484 | var compiled = bench.compiled = createFunction(preprocess('t$'), interpolate(
2485 | preprocess(deferred
2486 | ? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' +
2487 | // when `deferred.cycles` is `0` then...
2488 | 'if(!d$.cycles){' +
2489 | // set `deferred.fn`
2490 | 'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' +
2491 | // set `deferred.teardown`
2492 | 'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' +
2493 | // execute the benchmark's `setup`
2494 | 'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' +
2495 | // start timer
2496 | 't$.start(d$);' +
2497 | // execute `deferred.fn` and return a dummy object
2498 | '}d$.fn();return{}'
2499 |
2500 | : 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count,n$=t$.ns;#{setup}\n#{begin};' +
2501 | 'while(i$--){#{fn}\n}#{end};#{teardown}\nreturn{elapsed:r$,uid:"#{uid}"}'),
2502 | source
2503 | ));
2504 |
2505 | try {
2506 | if (isEmpty) {
2507 | // Firefox may remove dead code from Function#toString results
2508 | // http://bugzil.la/536085
2509 | throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.');
2510 | }
2511 | else if (!deferred) {
2512 | // pretest to determine if compiled code is exits early, usually by a
2513 | // rogue `return` statement, by checking for a return object with the uid
2514 | bench.count = 1;
2515 | compiled = (compiled.call(bench, timer) || {}).uid == uid && compiled;
2516 | bench.count = count;
2517 | }
2518 | } catch(e) {
2519 | compiled = null;
2520 | clone.error = e || new Error(String(e));
2521 | bench.count = count;
2522 | }
2523 | // fallback when a test exits early or errors during pretest
2524 | if (decompilable && !compiled && !deferred && !isEmpty) {
2525 | compiled = createFunction(preprocess('t$'), interpolate(
2526 | preprocess(
2527 | (clone.error && !stringable
2528 | ? 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count'
2529 | : 'function f$(){#{fn}\n}var r$,s$,m$=this,i$=m$.count'
2530 | ) +
2531 | ',n$=t$.ns;#{setup}\n#{begin};m$.f$=f$;while(i$--){m$.f$()}#{end};' +
2532 | 'delete m$.f$;#{teardown}\nreturn{elapsed:r$}'
2533 | ),
2534 | source
2535 | ));
2536 |
2537 | try {
2538 | // pretest one more time to check for errors
2539 | bench.count = 1;
2540 | compiled.call(bench, timer);
2541 | bench.compiled = compiled;
2542 | bench.count = count;
2543 | delete clone.error;
2544 | }
2545 | catch(e) {
2546 | bench.count = count;
2547 | if (clone.error) {
2548 | compiled = null;
2549 | } else {
2550 | bench.compiled = compiled;
2551 | clone.error = e || new Error(String(e));
2552 | }
2553 | }
2554 | }
2555 | // assign `compiled` to `clone` before calling in case a deferred benchmark
2556 | // immediately calls `deferred.resolve()`
2557 | clone.compiled = compiled;
2558 | // if no errors run the full test loop
2559 | if (!clone.error) {
2560 | result = compiled.call(deferred || bench, timer).elapsed;
2561 | }
2562 | return result;
2563 | };
2564 |
2565 | /*------------------------------------------------------------------------*/
2566 |
2567 | /**
2568 | * Gets the current timer's minimum resolution (secs).
2569 | */
2570 | function getRes(unit) {
2571 | var measured,
2572 | begin,
2573 | count = 30,
2574 | divisor = 1e3,
2575 | ns = timer.ns,
2576 | sample = [];
2577 |
2578 | // get average smallest measurable time
2579 | while (count--) {
2580 | if (unit == 'us') {
2581 | divisor = 1e6;
2582 | if (ns.stop) {
2583 | ns.start();
2584 | while (!(measured = ns.microseconds())) { }
2585 | } else if (ns[perfName]) {
2586 | divisor = 1e3;
2587 | measured = Function('n', 'var r,s=n.' + perfName + '();while(!(r=n.' + perfName + '()-s)){};return r')(ns);
2588 | } else {
2589 | begin = ns();
2590 | while (!(measured = ns() - begin)) { }
2591 | }
2592 | }
2593 | else if (unit == 'ns') {
2594 | divisor = 1e9;
2595 | if (ns.nanoTime) {
2596 | begin = ns.nanoTime();
2597 | while (!(measured = ns.nanoTime() - begin)) { }
2598 | } else {
2599 | begin = (begin = ns())[0] + (begin[1] / divisor);
2600 | while (!(measured = ((measured = ns())[0] + (measured[1] / divisor)) - begin)) { }
2601 | divisor = 1;
2602 | }
2603 | }
2604 | else {
2605 | begin = new ns;
2606 | while (!(measured = new ns - begin)) { }
2607 | }
2608 | // check for broken timers (nanoTime may have issues)
2609 | // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/
2610 | if (measured > 0) {
2611 | sample.push(measured);
2612 | } else {
2613 | sample.push(Infinity);
2614 | break;
2615 | }
2616 | }
2617 | // convert to seconds
2618 | return getMean(sample) / divisor;
2619 | }
2620 |
2621 | /**
2622 | * Replaces all occurrences of `$` with a unique number and
2623 | * template tokens with content.
2624 | */
2625 | function preprocess(code) {
2626 | return interpolate(code, template).replace(/\$/g, /\d+/.exec(uid));
2627 | }
2628 |
2629 | /*------------------------------------------------------------------------*/
2630 |
2631 | // detect nanosecond support from a Java applet
2632 | each(doc && doc.applets || [], function(element) {
2633 | return !(timer.ns = applet = 'nanoTime' in element && element);
2634 | });
2635 |
2636 | // check type in case Safari returns an object instead of a number
2637 | try {
2638 | if (typeof timer.ns.nanoTime() == 'number') {
2639 | timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' });
2640 | }
2641 | } catch(e) { }
2642 |
2643 | // detect Chrome's microsecond timer:
2644 | // enable benchmarking via the --enable-benchmarking command
2645 | // line switch in at least Chrome 7 to use chrome.Interval
2646 | try {
2647 | if ((timer.ns = new (window.chrome || window.chromium).Interval)) {
2648 | timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
2649 | }
2650 | } catch(e) { }
2651 |
2652 | // detect `performance.now` microsecond resolution timer
2653 | if ((timer.ns = perfName && perfObject)) {
2654 | timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
2655 | }
2656 |
2657 | // detect Node's nanosecond resolution timer available in Node >= 0.8
2658 | if (processObject && typeof (timer.ns = processObject.hrtime) == 'function') {
2659 | timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' });
2660 | }
2661 |
2662 | // detect Wade Simmons' Node microtime module
2663 | if (microtimeObject && typeof (timer.ns = microtimeObject.now) == 'function') {
2664 | timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
2665 | }
2666 |
2667 | // pick timer with highest resolution
2668 | timer = reduce(timers, function(timer, other) {
2669 | return other.res < timer.res ? other : timer;
2670 | });
2671 |
2672 | // remove unused applet
2673 | if (timer.unit != 'ns' && applet) {
2674 | applet = destroyElement(applet);
2675 | }
2676 | // error if there are no working timers
2677 | if (timer.res == Infinity) {
2678 | throw new Error('Benchmark.js was unable to find a working timer.');
2679 | }
2680 | // use API of chosen timer
2681 | if (timer.unit == 'ns') {
2682 | if (timer.ns.nanoTime) {
2683 | extend(template, {
2684 | 'begin': 's$=n$.nanoTime()',
2685 | 'end': 'r$=(n$.nanoTime()-s$)/1e9'
2686 | });
2687 | } else {
2688 | extend(template, {
2689 | 'begin': 's$=n$()',
2690 | 'end': 'r$=n$(s$);r$=r$[0]+(r$[1]/1e9)'
2691 | });
2692 | }
2693 | }
2694 | else if (timer.unit == 'us') {
2695 | if (timer.ns.stop) {
2696 | extend(template, {
2697 | 'begin': 's$=n$.start()',
2698 | 'end': 'r$=n$.microseconds()/1e6'
2699 | });
2700 | } else if (perfName) {
2701 | extend(template, {
2702 | 'begin': 's$=n$.' + perfName + '()',
2703 | 'end': 'r$=(n$.' + perfName + '()-s$)/1e3'
2704 | });
2705 | } else {
2706 | extend(template, {
2707 | 'begin': 's$=n$()',
2708 | 'end': 'r$=(n$()-s$)/1e6'
2709 | });
2710 | }
2711 | }
2712 |
2713 | // define `timer` methods
2714 | timer.start = createFunction(preprocess('o$'),
2715 | preprocess('var n$=this.ns,#{begin};o$.elapsed=0;o$.timeStamp=s$'));
2716 |
2717 | timer.stop = createFunction(preprocess('o$'),
2718 | preprocess('var n$=this.ns,s$=o$.timeStamp,#{end};o$.elapsed=r$'));
2719 |
2720 | // resolve time span required to achieve a percent uncertainty of at most 1%
2721 | // http://spiff.rit.edu/classes/phys273/uncert/uncert.html
2722 | options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05));
2723 | return clock.apply(null, arguments);
2724 | }
2725 |
2726 | /*--------------------------------------------------------------------------*/
2727 |
2728 | /**
2729 | * Computes stats on benchmark results.
2730 | *
2731 | * @private
2732 | * @param {Object} bench The benchmark instance.
2733 | * @param {Object} options The options object.
2734 | */
2735 | function compute(bench, options) {
2736 | options || (options = {});
2737 |
2738 | var async = options.async,
2739 | elapsed = 0,
2740 | initCount = bench.initCount,
2741 | minSamples = bench.minSamples,
2742 | queue = [],
2743 | sample = bench.stats.sample;
2744 |
2745 | /**
2746 | * Adds a clone to the queue.
2747 | */
2748 | function enqueue() {
2749 | queue.push(bench.clone({
2750 | '_original': bench,
2751 | 'events': {
2752 | 'abort': [update],
2753 | 'cycle': [update],
2754 | 'error': [update],
2755 | 'start': [update]
2756 | }
2757 | }));
2758 | }
2759 |
2760 | /**
2761 | * Updates the clone/original benchmarks to keep their data in sync.
2762 | */
2763 | function update(event) {
2764 | var clone = this,
2765 | type = event.type;
2766 |
2767 | if (bench.running) {
2768 | if (type == 'start') {
2769 | // Note: `clone.minTime` prop is inited in `clock()`
2770 | clone.count = bench.initCount;
2771 | }
2772 | else {
2773 | if (type == 'error') {
2774 | bench.error = clone.error;
2775 | }
2776 | if (type == 'abort') {
2777 | bench.abort();
2778 | bench.emit('cycle');
2779 | } else {
2780 | event.currentTarget = event.target = bench;
2781 | bench.emit(event);
2782 | }
2783 | }
2784 | } else if (bench.aborted) {
2785 | // clear abort listeners to avoid triggering bench's abort/cycle again
2786 | clone.events.abort.length = 0;
2787 | clone.abort();
2788 | }
2789 | }
2790 |
2791 | /**
2792 | * Determines if more clones should be queued or if cycling should stop.
2793 | */
2794 | function evaluate(event) {
2795 | var critical,
2796 | df,
2797 | mean,
2798 | moe,
2799 | rme,
2800 | sd,
2801 | sem,
2802 | variance,
2803 | clone = event.target,
2804 | done = bench.aborted,
2805 | now = +new Date,
2806 | size = sample.push(clone.times.period),
2807 | maxedOut = size >= minSamples && (elapsed += now - clone.times.timeStamp) / 1e3 > bench.maxTime,
2808 | times = bench.times,
2809 | varOf = function(sum, x) { return sum + pow(x - mean, 2); };
2810 |
2811 | // exit early for aborted or unclockable tests
2812 | if (done || clone.hz == Infinity) {
2813 | maxedOut = !(size = sample.length = queue.length = 0);
2814 | }
2815 |
2816 | if (!done) {
2817 | // sample mean (estimate of the population mean)
2818 | mean = getMean(sample);
2819 | // sample variance (estimate of the population variance)
2820 | variance = reduce(sample, varOf, 0) / (size - 1) || 0;
2821 | // sample standard deviation (estimate of the population standard deviation)
2822 | sd = sqrt(variance);
2823 | // standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean)
2824 | sem = sd / sqrt(size);
2825 | // degrees of freedom
2826 | df = size - 1;
2827 | // critical value
2828 | critical = tTable[Math.round(df) || 1] || tTable.infinity;
2829 | // margin of error
2830 | moe = sem * critical;
2831 | // relative margin of error
2832 | rme = (moe / mean) * 100 || 0;
2833 |
2834 | extend(bench.stats, {
2835 | 'deviation': sd,
2836 | 'mean': mean,
2837 | 'moe': moe,
2838 | 'rme': rme,
2839 | 'sem': sem,
2840 | 'variance': variance
2841 | });
2842 |
2843 | // Abort the cycle loop when the minimum sample size has been collected
2844 | // and the elapsed time exceeds the maximum time allowed per benchmark.
2845 | // We don't count cycle delays toward the max time because delays may be
2846 | // increased by browsers that clamp timeouts for inactive tabs.
2847 | // https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs
2848 | if (maxedOut) {
2849 | // reset the `initCount` in case the benchmark is rerun
2850 | bench.initCount = initCount;
2851 | bench.running = false;
2852 | done = true;
2853 | times.elapsed = (now - times.timeStamp) / 1e3;
2854 | }
2855 | if (bench.hz != Infinity) {
2856 | bench.hz = 1 / mean;
2857 | times.cycle = mean * bench.count;
2858 | times.period = mean;
2859 | }
2860 | }
2861 | // if time permits, increase sample size to reduce the margin of error
2862 | if (queue.length < 2 && !maxedOut) {
2863 | enqueue();
2864 | }
2865 | // abort the invoke cycle when done
2866 | event.aborted = done;
2867 | }
2868 |
2869 | // init queue and begin
2870 | enqueue();
2871 | invoke(queue, {
2872 | 'name': 'run',
2873 | 'args': { 'async': async },
2874 | 'queued': true,
2875 | 'onCycle': evaluate,
2876 | 'onComplete': function() { bench.emit('complete'); }
2877 | });
2878 | }
2879 |
2880 | /*--------------------------------------------------------------------------*/
2881 |
2882 | /**
2883 | * Cycles a benchmark until a run `count` can be established.
2884 | *
2885 | * @private
2886 | * @param {Object} clone The cloned benchmark instance.
2887 | * @param {Object} options The options object.
2888 | */
2889 | function cycle(clone, options) {
2890 | options || (options = {});
2891 |
2892 | var deferred;
2893 | if (clone instanceof Deferred) {
2894 | deferred = clone;
2895 | clone = clone.benchmark;
2896 | }
2897 |
2898 | var clocked,
2899 | cycles,
2900 | divisor,
2901 | event,
2902 | minTime,
2903 | period,
2904 | async = options.async,
2905 | bench = clone._original,
2906 | count = clone.count,
2907 | times = clone.times;
2908 |
2909 | // continue, if not aborted between cycles
2910 | if (clone.running) {
2911 | // `minTime` is set to `Benchmark.options.minTime` in `clock()`
2912 | cycles = ++clone.cycles;
2913 | clocked = deferred ? deferred.elapsed : clock(clone);
2914 | minTime = clone.minTime;
2915 |
2916 | if (cycles > bench.cycles) {
2917 | bench.cycles = cycles;
2918 | }
2919 | if (clone.error) {
2920 | event = Event('error');
2921 | event.message = clone.error;
2922 | clone.emit(event);
2923 | if (!event.cancelled) {
2924 | clone.abort();
2925 | }
2926 | }
2927 | }
2928 |
2929 | // continue, if not errored
2930 | if (clone.running) {
2931 | // time taken to complete last test cycle
2932 | bench.times.cycle = times.cycle = clocked;
2933 | // seconds per operation
2934 | period = bench.times.period = times.period = clocked / count;
2935 | // ops per second
2936 | bench.hz = clone.hz = 1 / period;
2937 | // avoid working our way up to this next time
2938 | bench.initCount = clone.initCount = count;
2939 | // do we need to do another cycle?
2940 | clone.running = clocked < minTime;
2941 |
2942 | if (clone.running) {
2943 | // tests may clock at `0` when `initCount` is a small number,
2944 | // to avoid that we set its count to something a bit higher
2945 | if (!clocked && (divisor = divisors[clone.cycles]) != null) {
2946 | count = floor(4e6 / divisor);
2947 | }
2948 | // calculate how many more iterations it will take to achive the `minTime`
2949 | if (count <= clone.count) {
2950 | count += Math.ceil((minTime - clocked) / period);
2951 | }
2952 | clone.running = count != Infinity;
2953 | }
2954 | }
2955 | // should we exit early?
2956 | event = Event('cycle');
2957 | clone.emit(event);
2958 | if (event.aborted) {
2959 | clone.abort();
2960 | }
2961 | // figure out what to do next
2962 | if (clone.running) {
2963 | // start a new cycle
2964 | clone.count = count;
2965 | if (deferred) {
2966 | clone.compiled.call(deferred, timer);
2967 | } else if (async) {
2968 | delay(clone, function() { cycle(clone, options); });
2969 | } else {
2970 | cycle(clone);
2971 | }
2972 | }
2973 | else {
2974 | // fix TraceMonkey bug associated with clock fallbacks
2975 | // http://bugzil.la/509069
2976 | if (support.browser) {
2977 | runScript(uid + '=1;delete ' + uid);
2978 | }
2979 | // done
2980 | clone.emit('complete');
2981 | }
2982 | }
2983 |
2984 | /*--------------------------------------------------------------------------*/
2985 |
2986 | /**
2987 | * Runs the benchmark.
2988 | *
2989 | * @memberOf Benchmark
2990 | * @param {Object} [options={}] Options object.
2991 | * @returns {Object} The benchmark instance.
2992 | * @example
2993 | *
2994 | * // basic usage
2995 | * bench.run();
2996 | *
2997 | * // or with options
2998 | * bench.run({ 'async': true });
2999 | */
3000 | function run(options) {
3001 | var me = this,
3002 | event = Event('start');
3003 |
3004 | // set `running` to `false` so `reset()` won't call `abort()`
3005 | me.running = false;
3006 | me.reset();
3007 | me.running = true;
3008 |
3009 | me.count = me.initCount;
3010 | me.times.timeStamp = +new Date;
3011 | me.emit(event);
3012 |
3013 | if (!event.cancelled) {
3014 | options = { 'async': ((options = options && options.async) == null ? me.async : options) && support.timeout };
3015 |
3016 | // for clones created within `compute()`
3017 | if (me._original) {
3018 | if (me.defer) {
3019 | Deferred(me);
3020 | } else {
3021 | cycle(me, options);
3022 | }
3023 | }
3024 | // for original benchmarks
3025 | else {
3026 | compute(me, options);
3027 | }
3028 | }
3029 | return me;
3030 | }
3031 |
3032 | /*--------------------------------------------------------------------------*/
3033 |
3034 | // Firefox 1 erroneously defines variable and argument names of functions on
3035 | // the function itself as non-configurable properties with `undefined` values.
3036 | // The bugginess continues as the `Benchmark` constructor has an argument
3037 | // named `options` and Firefox 1 will not assign a value to `Benchmark.options`,
3038 | // making it non-writable in the process, unless it is the first property
3039 | // assigned by for-in loop of `extend()`.
3040 | extend(Benchmark, {
3041 |
3042 | /**
3043 | * The default options copied by benchmark instances.
3044 | *
3045 | * @static
3046 | * @memberOf Benchmark
3047 | * @type Object
3048 | */
3049 | 'options': {
3050 |
3051 | /**
3052 | * A flag to indicate that benchmark cycles will execute asynchronously
3053 | * by default.
3054 | *
3055 | * @memberOf Benchmark.options
3056 | * @type Boolean
3057 | */
3058 | 'async': false,
3059 |
3060 | /**
3061 | * A flag to indicate that the benchmark clock is deferred.
3062 | *
3063 | * @memberOf Benchmark.options
3064 | * @type Boolean
3065 | */
3066 | 'defer': false,
3067 |
3068 | /**
3069 | * The delay between test cycles (secs).
3070 | * @memberOf Benchmark.options
3071 | * @type Number
3072 | */
3073 | 'delay': 0.005,
3074 |
3075 | /**
3076 | * Displayed by Benchmark#toString when a `name` is not available
3077 | * (auto-generated if absent).
3078 | *
3079 | * @memberOf Benchmark.options
3080 | * @type String
3081 | */
3082 | 'id': undefined,
3083 |
3084 | /**
3085 | * The default number of times to execute a test on a benchmark's first cycle.
3086 | *
3087 | * @memberOf Benchmark.options
3088 | * @type Number
3089 | */
3090 | 'initCount': 1,
3091 |
3092 | /**
3093 | * The maximum time a benchmark is allowed to run before finishing (secs).
3094 | *
3095 | * Note: Cycle delays aren't counted toward the maximum time.
3096 | *
3097 | * @memberOf Benchmark.options
3098 | * @type Number
3099 | */
3100 | 'maxTime': 5,
3101 |
3102 | /**
3103 | * The minimum sample size required to perform statistical analysis.
3104 | *
3105 | * @memberOf Benchmark.options
3106 | * @type Number
3107 | */
3108 | 'minSamples': 5,
3109 |
3110 | /**
3111 | * The time needed to reduce the percent uncertainty of measurement to 1% (secs).
3112 | *
3113 | * @memberOf Benchmark.options
3114 | * @type Number
3115 | */
3116 | 'minTime': 0,
3117 |
3118 | /**
3119 | * The name of the benchmark.
3120 | *
3121 | * @memberOf Benchmark.options
3122 | * @type String
3123 | */
3124 | 'name': undefined,
3125 |
3126 | /**
3127 | * An event listener called when the benchmark is aborted.
3128 | *
3129 | * @memberOf Benchmark.options
3130 | * @type Function
3131 | */
3132 | 'onAbort': undefined,
3133 |
3134 | /**
3135 | * An event listener called when the benchmark completes running.
3136 | *
3137 | * @memberOf Benchmark.options
3138 | * @type Function
3139 | */
3140 | 'onComplete': undefined,
3141 |
3142 | /**
3143 | * An event listener called after each run cycle.
3144 | *
3145 | * @memberOf Benchmark.options
3146 | * @type Function
3147 | */
3148 | 'onCycle': undefined,
3149 |
3150 | /**
3151 | * An event listener called when a test errors.
3152 | *
3153 | * @memberOf Benchmark.options
3154 | * @type Function
3155 | */
3156 | 'onError': undefined,
3157 |
3158 | /**
3159 | * An event listener called when the benchmark is reset.
3160 | *
3161 | * @memberOf Benchmark.options
3162 | * @type Function
3163 | */
3164 | 'onReset': undefined,
3165 |
3166 | /**
3167 | * An event listener called when the benchmark starts running.
3168 | *
3169 | * @memberOf Benchmark.options
3170 | * @type Function
3171 | */
3172 | 'onStart': undefined
3173 | },
3174 |
3175 | /**
3176 | * Platform object with properties describing things like browser name,
3177 | * version, and operating system.
3178 | *
3179 | * @static
3180 | * @memberOf Benchmark
3181 | * @type Object
3182 | */
3183 | 'platform': req('platform') || window.platform || {
3184 |
3185 | /**
3186 | * The platform description.
3187 | *
3188 | * @memberOf Benchmark.platform
3189 | * @type String
3190 | */
3191 | 'description': window.navigator && navigator.userAgent || null,
3192 |
3193 | /**
3194 | * The name of the browser layout engine.
3195 | *
3196 | * @memberOf Benchmark.platform
3197 | * @type String|Null
3198 | */
3199 | 'layout': null,
3200 |
3201 | /**
3202 | * The name of the product hosting the browser.
3203 | *
3204 | * @memberOf Benchmark.platform
3205 | * @type String|Null
3206 | */
3207 | 'product': null,
3208 |
3209 | /**
3210 | * The name of the browser/environment.
3211 | *
3212 | * @memberOf Benchmark.platform
3213 | * @type String|Null
3214 | */
3215 | 'name': null,
3216 |
3217 | /**
3218 | * The name of the product's manufacturer.
3219 | *
3220 | * @memberOf Benchmark.platform
3221 | * @type String|Null
3222 | */
3223 | 'manufacturer': null,
3224 |
3225 | /**
3226 | * The name of the operating system.
3227 | *
3228 | * @memberOf Benchmark.platform
3229 | * @type String|Null
3230 | */
3231 | 'os': null,
3232 |
3233 | /**
3234 | * The alpha/beta release indicator.
3235 | *
3236 | * @memberOf Benchmark.platform
3237 | * @type String|Null
3238 | */
3239 | 'prerelease': null,
3240 |
3241 | /**
3242 | * The browser/environment version.
3243 | *
3244 | * @memberOf Benchmark.platform
3245 | * @type String|Null
3246 | */
3247 | 'version': null,
3248 |
3249 | /**
3250 | * Return platform description when the platform object is coerced to a string.
3251 | *
3252 | * @memberOf Benchmark.platform
3253 | * @type Function
3254 | * @returns {String} The platform description.
3255 | */
3256 | 'toString': function() {
3257 | return this.description || '';
3258 | }
3259 | },
3260 |
3261 | /**
3262 | * The semantic version number.
3263 | *
3264 | * @static
3265 | * @memberOf Benchmark
3266 | * @type String
3267 | */
3268 | 'version': '1.0.0',
3269 |
3270 | // an object of environment/feature detection flags
3271 | 'support': support,
3272 |
3273 | // clone objects
3274 | 'deepClone': deepClone,
3275 |
3276 | // iteration utility
3277 | 'each': each,
3278 |
3279 | // augment objects
3280 | 'extend': extend,
3281 |
3282 | // generic Array#filter
3283 | 'filter': filter,
3284 |
3285 | // generic Array#forEach
3286 | 'forEach': forEach,
3287 |
3288 | // generic own property iteration utility
3289 | 'forOwn': forOwn,
3290 |
3291 | // converts a number to a comma-separated string
3292 | 'formatNumber': formatNumber,
3293 |
3294 | // generic Object#hasOwnProperty
3295 | // (trigger hasKey's lazy define before assigning it to Benchmark)
3296 | 'hasKey': (hasKey(Benchmark, ''), hasKey),
3297 |
3298 | // generic Array#indexOf
3299 | 'indexOf': indexOf,
3300 |
3301 | // template utility
3302 | 'interpolate': interpolate,
3303 |
3304 | // invokes a method on each item in an array
3305 | 'invoke': invoke,
3306 |
3307 | // generic Array#join for arrays and objects
3308 | 'join': join,
3309 |
3310 | // generic Array#map
3311 | 'map': map,
3312 |
3313 | // retrieves a property value from each item in an array
3314 | 'pluck': pluck,
3315 |
3316 | // generic Array#reduce
3317 | 'reduce': reduce
3318 | });
3319 |
3320 | /*--------------------------------------------------------------------------*/
3321 |
3322 | extend(Benchmark.prototype, {
3323 |
3324 | /**
3325 | * The number of times a test was executed.
3326 | *
3327 | * @memberOf Benchmark
3328 | * @type Number
3329 | */
3330 | 'count': 0,
3331 |
3332 | /**
3333 | * The number of cycles performed while benchmarking.
3334 | *
3335 | * @memberOf Benchmark
3336 | * @type Number
3337 | */
3338 | 'cycles': 0,
3339 |
3340 | /**
3341 | * The number of executions per second.
3342 | *
3343 | * @memberOf Benchmark
3344 | * @type Number
3345 | */
3346 | 'hz': 0,
3347 |
3348 | /**
3349 | * The compiled test function.
3350 | *
3351 | * @memberOf Benchmark
3352 | * @type Function|String
3353 | */
3354 | 'compiled': undefined,
3355 |
3356 | /**
3357 | * The error object if the test failed.
3358 | *
3359 | * @memberOf Benchmark
3360 | * @type Object
3361 | */
3362 | 'error': undefined,
3363 |
3364 | /**
3365 | * The test to benchmark.
3366 | *
3367 | * @memberOf Benchmark
3368 | * @type Function|String
3369 | */
3370 | 'fn': undefined,
3371 |
3372 | /**
3373 | * A flag to indicate if the benchmark is aborted.
3374 | *
3375 | * @memberOf Benchmark
3376 | * @type Boolean
3377 | */
3378 | 'aborted': false,
3379 |
3380 | /**
3381 | * A flag to indicate if the benchmark is running.
3382 | *
3383 | * @memberOf Benchmark
3384 | * @type Boolean
3385 | */
3386 | 'running': false,
3387 |
3388 | /**
3389 | * Compiled into the test and executed immediately **before** the test loop.
3390 | *
3391 | * @memberOf Benchmark
3392 | * @type Function|String
3393 | * @example
3394 | *
3395 | * // basic usage
3396 | * var bench = Benchmark({
3397 | * 'setup': function() {
3398 | * var c = this.count,
3399 | * element = document.getElementById('container');
3400 | * while (c--) {
3401 | * element.appendChild(document.createElement('div'));
3402 | * }
3403 | * },
3404 | * 'fn': function() {
3405 | * element.removeChild(element.lastChild);
3406 | * }
3407 | * });
3408 | *
3409 | * // compiles to something like:
3410 | * var c = this.count,
3411 | * element = document.getElementById('container');
3412 | * while (c--) {
3413 | * element.appendChild(document.createElement('div'));
3414 | * }
3415 | * var start = new Date;
3416 | * while (count--) {
3417 | * element.removeChild(element.lastChild);
3418 | * }
3419 | * var end = new Date - start;
3420 | *
3421 | * // or using strings
3422 | * var bench = Benchmark({
3423 | * 'setup': '\
3424 | * var a = 0;\n\
3425 | * (function() {\n\
3426 | * (function() {\n\
3427 | * (function() {',
3428 | * 'fn': 'a += 1;',
3429 | * 'teardown': '\
3430 | * }())\n\
3431 | * }())\n\
3432 | * }())'
3433 | * });
3434 | *
3435 | * // compiles to something like:
3436 | * var a = 0;
3437 | * (function() {
3438 | * (function() {
3439 | * (function() {
3440 | * var start = new Date;
3441 | * while (count--) {
3442 | * a += 1;
3443 | * }
3444 | * var end = new Date - start;
3445 | * }())
3446 | * }())
3447 | * }())
3448 | */
3449 | 'setup': noop,
3450 |
3451 | /**
3452 | * Compiled into the test and executed immediately **after** the test loop.
3453 | *
3454 | * @memberOf Benchmark
3455 | * @type Function|String
3456 | */
3457 | 'teardown': noop,
3458 |
3459 | /**
3460 | * An object of stats including mean, margin or error, and standard deviation.
3461 | *
3462 | * @memberOf Benchmark
3463 | * @type Object
3464 | */
3465 | 'stats': {
3466 |
3467 | /**
3468 | * The margin of error.
3469 | *
3470 | * @memberOf Benchmark#stats
3471 | * @type Number
3472 | */
3473 | 'moe': 0,
3474 |
3475 | /**
3476 | * The relative margin of error (expressed as a percentage of the mean).
3477 | *
3478 | * @memberOf Benchmark#stats
3479 | * @type Number
3480 | */
3481 | 'rme': 0,
3482 |
3483 | /**
3484 | * The standard error of the mean.
3485 | *
3486 | * @memberOf Benchmark#stats
3487 | * @type Number
3488 | */
3489 | 'sem': 0,
3490 |
3491 | /**
3492 | * The sample standard deviation.
3493 | *
3494 | * @memberOf Benchmark#stats
3495 | * @type Number
3496 | */
3497 | 'deviation': 0,
3498 |
3499 | /**
3500 | * The sample arithmetic mean.
3501 | *
3502 | * @memberOf Benchmark#stats
3503 | * @type Number
3504 | */
3505 | 'mean': 0,
3506 |
3507 | /**
3508 | * The array of sampled periods.
3509 | *
3510 | * @memberOf Benchmark#stats
3511 | * @type Array
3512 | */
3513 | 'sample': [],
3514 |
3515 | /**
3516 | * The sample variance.
3517 | *
3518 | * @memberOf Benchmark#stats
3519 | * @type Number
3520 | */
3521 | 'variance': 0
3522 | },
3523 |
3524 | /**
3525 | * An object of timing data including cycle, elapsed, period, start, and stop.
3526 | *
3527 | * @memberOf Benchmark
3528 | * @type Object
3529 | */
3530 | 'times': {
3531 |
3532 | /**
3533 | * The time taken to complete the last cycle (secs).
3534 | *
3535 | * @memberOf Benchmark#times
3536 | * @type Number
3537 | */
3538 | 'cycle': 0,
3539 |
3540 | /**
3541 | * The time taken to complete the benchmark (secs).
3542 | *
3543 | * @memberOf Benchmark#times
3544 | * @type Number
3545 | */
3546 | 'elapsed': 0,
3547 |
3548 | /**
3549 | * The time taken to execute the test once (secs).
3550 | *
3551 | * @memberOf Benchmark#times
3552 | * @type Number
3553 | */
3554 | 'period': 0,
3555 |
3556 | /**
3557 | * A timestamp of when the benchmark started (ms).
3558 | *
3559 | * @memberOf Benchmark#times
3560 | * @type Number
3561 | */
3562 | 'timeStamp': 0
3563 | },
3564 |
3565 | // aborts benchmark (does not record times)
3566 | 'abort': abort,
3567 |
3568 | // creates a new benchmark using the same test and options
3569 | 'clone': clone,
3570 |
3571 | // compares benchmark's hertz with another
3572 | 'compare': compare,
3573 |
3574 | // executes listeners
3575 | 'emit': emit,
3576 |
3577 | // get listeners
3578 | 'listeners': listeners,
3579 |
3580 | // unregister listeners
3581 | 'off': off,
3582 |
3583 | // register listeners
3584 | 'on': on,
3585 |
3586 | // reset benchmark properties
3587 | 'reset': reset,
3588 |
3589 | // runs the benchmark
3590 | 'run': run,
3591 |
3592 | // pretty print benchmark info
3593 | 'toString': toStringBench
3594 | });
3595 |
3596 | /*--------------------------------------------------------------------------*/
3597 |
3598 | extend(Deferred.prototype, {
3599 |
3600 | /**
3601 | * The deferred benchmark instance.
3602 | *
3603 | * @memberOf Benchmark.Deferred
3604 | * @type Object
3605 | */
3606 | 'benchmark': null,
3607 |
3608 | /**
3609 | * The number of deferred cycles performed while benchmarking.
3610 | *
3611 | * @memberOf Benchmark.Deferred
3612 | * @type Number
3613 | */
3614 | 'cycles': 0,
3615 |
3616 | /**
3617 | * The time taken to complete the deferred benchmark (secs).
3618 | *
3619 | * @memberOf Benchmark.Deferred
3620 | * @type Number
3621 | */
3622 | 'elapsed': 0,
3623 |
3624 | /**
3625 | * A timestamp of when the deferred benchmark started (ms).
3626 | *
3627 | * @memberOf Benchmark.Deferred
3628 | * @type Number
3629 | */
3630 | 'timeStamp': 0,
3631 |
3632 | // cycles/completes the deferred benchmark
3633 | 'resolve': resolve
3634 | });
3635 |
3636 | /*--------------------------------------------------------------------------*/
3637 |
3638 | extend(Event.prototype, {
3639 |
3640 | /**
3641 | * A flag to indicate if the emitters listener iteration is aborted.
3642 | *
3643 | * @memberOf Benchmark.Event
3644 | * @type Boolean
3645 | */
3646 | 'aborted': false,
3647 |
3648 | /**
3649 | * A flag to indicate if the default action is cancelled.
3650 | *
3651 | * @memberOf Benchmark.Event
3652 | * @type Boolean
3653 | */
3654 | 'cancelled': false,
3655 |
3656 | /**
3657 | * The object whose listeners are currently being processed.
3658 | *
3659 | * @memberOf Benchmark.Event
3660 | * @type Object
3661 | */
3662 | 'currentTarget': undefined,
3663 |
3664 | /**
3665 | * The return value of the last executed listener.
3666 | *
3667 | * @memberOf Benchmark.Event
3668 | * @type Mixed
3669 | */
3670 | 'result': undefined,
3671 |
3672 | /**
3673 | * The object to which the event was originally emitted.
3674 | *
3675 | * @memberOf Benchmark.Event
3676 | * @type Object
3677 | */
3678 | 'target': undefined,
3679 |
3680 | /**
3681 | * A timestamp of when the event was created (ms).
3682 | *
3683 | * @memberOf Benchmark.Event
3684 | * @type Number
3685 | */
3686 | 'timeStamp': 0,
3687 |
3688 | /**
3689 | * The event type.
3690 | *
3691 | * @memberOf Benchmark.Event
3692 | * @type String
3693 | */
3694 | 'type': ''
3695 | });
3696 |
3697 | /*--------------------------------------------------------------------------*/
3698 |
3699 | /**
3700 | * The default options copied by suite instances.
3701 | *
3702 | * @static
3703 | * @memberOf Benchmark.Suite
3704 | * @type Object
3705 | */
3706 | Suite.options = {
3707 |
3708 | /**
3709 | * The name of the suite.
3710 | *
3711 | * @memberOf Benchmark.Suite.options
3712 | * @type String
3713 | */
3714 | 'name': undefined
3715 | };
3716 |
3717 | /*--------------------------------------------------------------------------*/
3718 |
3719 | extend(Suite.prototype, {
3720 |
3721 | /**
3722 | * The number of benchmarks in the suite.
3723 | *
3724 | * @memberOf Benchmark.Suite
3725 | * @type Number
3726 | */
3727 | 'length': 0,
3728 |
3729 | /**
3730 | * A flag to indicate if the suite is aborted.
3731 | *
3732 | * @memberOf Benchmark.Suite
3733 | * @type Boolean
3734 | */
3735 | 'aborted': false,
3736 |
3737 | /**
3738 | * A flag to indicate if the suite is running.
3739 | *
3740 | * @memberOf Benchmark.Suite
3741 | * @type Boolean
3742 | */
3743 | 'running': false,
3744 |
3745 | /**
3746 | * An `Array#forEach` like method.
3747 | * Callbacks may terminate the loop by explicitly returning `false`.
3748 | *
3749 | * @memberOf Benchmark.Suite
3750 | * @param {Function} callback The function called per iteration.
3751 | * @returns {Object} The suite iterated over.
3752 | */
3753 | 'forEach': methodize(forEach),
3754 |
3755 | /**
3756 | * An `Array#indexOf` like method.
3757 | *
3758 | * @memberOf Benchmark.Suite
3759 | * @param {Mixed} value The value to search for.
3760 | * @returns {Number} The index of the matched value or `-1`.
3761 | */
3762 | 'indexOf': methodize(indexOf),
3763 |
3764 | /**
3765 | * Invokes a method on all benchmarks in the suite.
3766 | *
3767 | * @memberOf Benchmark.Suite
3768 | * @param {String|Object} name The name of the method to invoke OR options object.
3769 | * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
3770 | * @returns {Array} A new array of values returned from each method invoked.
3771 | */
3772 | 'invoke': methodize(invoke),
3773 |
3774 | /**
3775 | * Converts the suite of benchmarks to a string.
3776 | *
3777 | * @memberOf Benchmark.Suite
3778 | * @param {String} [separator=','] A string to separate each element of the array.
3779 | * @returns {String} The string.
3780 | */
3781 | 'join': [].join,
3782 |
3783 | /**
3784 | * An `Array#map` like method.
3785 | *
3786 | * @memberOf Benchmark.Suite
3787 | * @param {Function} callback The function called per iteration.
3788 | * @returns {Array} A new array of values returned by the callback.
3789 | */
3790 | 'map': methodize(map),
3791 |
3792 | /**
3793 | * Retrieves the value of a specified property from all benchmarks in the suite.
3794 | *
3795 | * @memberOf Benchmark.Suite
3796 | * @param {String} property The property to pluck.
3797 | * @returns {Array} A new array of property values.
3798 | */
3799 | 'pluck': methodize(pluck),
3800 |
3801 | /**
3802 | * Removes the last benchmark from the suite and returns it.
3803 | *
3804 | * @memberOf Benchmark.Suite
3805 | * @returns {Mixed} The removed benchmark.
3806 | */
3807 | 'pop': [].pop,
3808 |
3809 | /**
3810 | * Appends benchmarks to the suite.
3811 | *
3812 | * @memberOf Benchmark.Suite
3813 | * @returns {Number} The suite's new length.
3814 | */
3815 | 'push': [].push,
3816 |
3817 | /**
3818 | * Sorts the benchmarks of the suite.
3819 | *
3820 | * @memberOf Benchmark.Suite
3821 | * @param {Function} [compareFn=null] A function that defines the sort order.
3822 | * @returns {Object} The sorted suite.
3823 | */
3824 | 'sort': [].sort,
3825 |
3826 | /**
3827 | * An `Array#reduce` like method.
3828 | *
3829 | * @memberOf Benchmark.Suite
3830 | * @param {Function} callback The function called per iteration.
3831 | * @param {Mixed} accumulator Initial value of the accumulator.
3832 | * @returns {Mixed} The accumulator.
3833 | */
3834 | 'reduce': methodize(reduce),
3835 |
3836 | // aborts all benchmarks in the suite
3837 | 'abort': abortSuite,
3838 |
3839 | // adds a benchmark to the suite
3840 | 'add': add,
3841 |
3842 | // creates a new suite with cloned benchmarks
3843 | 'clone': cloneSuite,
3844 |
3845 | // executes listeners of a specified type
3846 | 'emit': emit,
3847 |
3848 | // creates a new suite of filtered benchmarks
3849 | 'filter': filterSuite,
3850 |
3851 | // get listeners
3852 | 'listeners': listeners,
3853 |
3854 | // unregister listeners
3855 | 'off': off,
3856 |
3857 | // register listeners
3858 | 'on': on,
3859 |
3860 | // resets all benchmarks in the suite
3861 | 'reset': resetSuite,
3862 |
3863 | // runs all benchmarks in the suite
3864 | 'run': runSuite,
3865 |
3866 | // array methods
3867 | 'concat': concat,
3868 |
3869 | 'reverse': reverse,
3870 |
3871 | 'shift': shift,
3872 |
3873 | 'slice': slice,
3874 |
3875 | 'splice': splice,
3876 |
3877 | 'unshift': unshift
3878 | });
3879 |
3880 | /*--------------------------------------------------------------------------*/
3881 |
3882 | // expose Deferred, Event and Suite
3883 | extend(Benchmark, {
3884 | 'Deferred': Deferred,
3885 | 'Event': Event,
3886 | 'Suite': Suite
3887 | });
3888 |
3889 | // expose Benchmark
3890 | // some AMD build optimizers, like r.js, check for specific condition patterns like the following:
3891 | if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
3892 | // define as an anonymous module so, through path mapping, it can be aliased
3893 | define(function() {
3894 | return Benchmark;
3895 | });
3896 | }
3897 | // check for `exports` after `define` in case a build optimizer adds an `exports` object
3898 | else if (freeExports) {
3899 | // in Node.js or RingoJS v0.8.0+
3900 | if (typeof module == 'object' && module && module.exports == freeExports) {
3901 | (module.exports = Benchmark).Benchmark = Benchmark;
3902 | }
3903 | // in Narwhal or RingoJS v0.7.0-
3904 | else {
3905 | freeExports.Benchmark = Benchmark;
3906 | }
3907 | }
3908 | // in a browser or Rhino
3909 | else {
3910 | // use square bracket notation so Closure Compiler won't munge `Benchmark`
3911 | // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
3912 | window['Benchmark'] = Benchmark;
3913 | }
3914 |
3915 | // trigger clock's lazy define early to avoid a security error
3916 | if (support.air) {
3917 | clock({ '_original': { 'fn': noop, 'count': 1, 'options': {} } });
3918 | }
3919 | }(this));
3920 |
--------------------------------------------------------------------------------
/test/helper.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var helper = {};
3 |
4 | // Used in IE debugging
5 | helper.ppObject = function(obj) {
6 | var result = '\n{\n';
7 |
8 | for(var name in obj) {
9 | result += '\t' + name + ': ' + obj[name] + '\n'
10 | }
11 |
12 | console.log(result + '}');
13 | };
14 |
15 | helper.$ = function(selector, context) {
16 | var elem = $(selector, context).get(0);
17 | var that = {};
18 |
19 | that.attr = function(name, value) {
20 | elem.setAttribute(name, value);
21 | };
22 | that.append = function(html) {
23 | elem.appendChild(html);
24 | };
25 | that.remove = function() {
26 | if(elem.parentNode) elem.parentNode.removeChild(elem);
27 | };
28 | that.content = function(text) {
29 | elem.textContent = text;
30 | };
31 |
32 | return that;
33 | };
34 |
35 | window.helper = helper;
36 | }());
37 |
--------------------------------------------------------------------------------
/test/integration.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | jQuery Observe Tests
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
25 |
26 |
27 |
28 |