NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / get / get-debug.js
blob5248bc0ed1f5e46a944f3bfff53bd3af5b7f3662
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('get', function (Y, NAME) {
10 /*jslint boss:true, expr:true, laxbreak: true */
12 /**
13 Provides dynamic loading of remote JavaScript and CSS resources.
15 @module get
16 @class Get
17 @static
18 **/
20 var Lang = Y.Lang,
22     CUSTOM_ATTRS, // defined lazily in Y.Get.Transaction._createNode()
24     Get, Transaction;
26 Y.Get = Get = {
27     // -- Public Properties ----------------------------------------------------
29     /**
30     Default options for CSS requests. Options specified here will override
31     global defaults for CSS requests.
33     See the `options` property for all available options.
35     @property cssOptions
36     @type Object
37     @static
38     @since 3.5.0
39     **/
40     cssOptions: {
41         attributes: {
42             rel: 'stylesheet'
43         },
45         doc         : Y.config.linkDoc || Y.config.doc,
46         pollInterval: 50
47     },
49     /**
50     Default options for JS requests. Options specified here will override global
51     defaults for JS requests.
53     See the `options` property for all available options.
55     @property jsOptions
56     @type Object
57     @static
58     @since 3.5.0
59     **/
60     jsOptions: {
61         autopurge: true,
62         doc      : Y.config.scriptDoc || Y.config.doc
63     },
65     /**
66     Default options to use for all requests.
68     Note that while all available options are documented here for ease of
69     discovery, some options (like callback functions) only make sense at the
70     transaction level.
72     Callback functions specified via the options object or the `options`
73     parameter of the `css()`, `js()`, or `load()` methods will receive the
74     transaction object as a parameter. See `Y.Get.Transaction` for details on
75     the properties and methods available on transactions.
77     @static
78     @since 3.5.0
79     @property {Object} options
81     @property {Boolean} [options.async=false] Whether or not to load scripts
82         asynchronously, meaning they're requested in parallel and execution
83         order is not guaranteed. Has no effect on CSS, since CSS is always
84         loaded asynchronously.
86     @property {Object} [options.attributes] HTML attribute name/value pairs that
87         should be added to inserted nodes. By default, the `charset` attribute
88         will be set to "utf-8" and nodes will be given an auto-generated `id`
89         attribute, but you can override these with your own values if desired.
91     @property {Boolean} [options.autopurge] Whether or not to automatically
92         purge inserted nodes after the purge threshold is reached. This is
93         `true` by default for JavaScript, but `false` for CSS since purging a
94         CSS node will also remove any styling applied by the referenced file.
96     @property {Object} [options.context] `this` object to use when calling
97         callback functions. Defaults to the transaction object.
99     @property {Mixed} [options.data] Arbitrary data object to pass to "on*"
100         callbacks.
102     @property {Document} [options.doc] Document into which nodes should be
103         inserted. By default, the current document is used.
105     @property {HTMLElement|String} [options.insertBefore] HTML element or id
106         string of an element before which all generated nodes should be
107         inserted. If not specified, Get will automatically determine the best
108         place to insert nodes for maximum compatibility.
110     @property {Function} [options.onEnd] Callback to execute after a transaction
111         is complete, regardless of whether it succeeded or failed.
113     @property {Function} [options.onFailure] Callback to execute after a
114         transaction fails, times out, or is aborted.
116     @property {Function} [options.onProgress] Callback to execute after each
117         individual request in a transaction either succeeds or fails.
119     @property {Function} [options.onSuccess] Callback to execute after a
120         transaction completes successfully with no errors. Note that in browsers
121         that don't support the `error` event on CSS `<link>` nodes, a failed CSS
122         request may still be reported as a success because in these browsers
123         it can be difficult or impossible to distinguish between success and
124         failure for CSS resources.
126     @property {Function} [options.onTimeout] Callback to execute after a
127         transaction times out.
129     @property {Number} [options.pollInterval=50] Polling interval (in
130         milliseconds) for detecting CSS load completion in browsers that don't
131         support the `load` event on `<link>` nodes. This isn't used for
132         JavaScript.
134     @property {Number} [options.purgethreshold=20] Number of nodes to insert
135         before triggering an automatic purge when `autopurge` is `true`.
137     @property {Number} [options.timeout] Number of milliseconds to wait before
138         aborting a transaction. When a timeout occurs, the `onTimeout` callback
139         is called, followed by `onFailure` and finally `onEnd`. By default,
140         there is no timeout.
142     @property {String} [options.type] Resource type ("css" or "js"). This option
143         is set automatically by the `css()` and `js()` functions and will be
144         ignored there, but may be useful when using the `load()` function. If
145         not specified, the type will be inferred from the URL, defaulting to
146         "js" if the URL doesn't contain a recognizable file extension.
147     **/
148     options: {
149         attributes: {
150             charset: 'utf-8'
151         },
153         purgethreshold: 20
154     },
156     // -- Protected Properties -------------------------------------------------
158     /**
159     Regex that matches a CSS URL. Used to guess the file type when it's not
160     specified.
162     @property REGEX_CSS
163     @type RegExp
164     @final
165     @protected
166     @static
167     @since 3.5.0
168     **/
169     REGEX_CSS: /\.css(?:[?;].*)?$/i,
171     /**
172     Regex that matches a JS URL. Used to guess the file type when it's not
173     specified.
175     @property REGEX_JS
176     @type RegExp
177     @final
178     @protected
179     @static
180     @since 3.5.0
181     **/
182     REGEX_JS : /\.js(?:[?;].*)?$/i,
184     /**
185     Contains information about the current environment, such as what script and
186     link injection features it supports.
188     This object is created and populated the first time the `_getEnv()` method
189     is called.
191     @property _env
192     @type Object
193     @protected
194     @static
195     @since 3.5.0
196     **/
198     /**
199     Mapping of document _yuid strings to <head> or <base> node references so we
200     don't have to look the node up each time we want to insert a request node.
202     @property _insertCache
203     @type Object
204     @protected
205     @static
206     @since 3.5.0
207     **/
208     _insertCache: {},
210     /**
211     Information about the currently pending transaction, if any.
213     This is actually an object with two properties: `callback`, containing the
214     optional callback passed to `css()`, `load()`, or `js()`; and `transaction`,
215     containing the actual transaction instance.
217     @property _pending
218     @type Object
219     @protected
220     @static
221     @since 3.5.0
222     **/
223     _pending: null,
225     /**
226     HTML nodes eligible to be purged next time autopurge is triggered.
228     @property _purgeNodes
229     @type HTMLElement[]
230     @protected
231     @static
232     @since 3.5.0
233     **/
234     _purgeNodes: [],
236     /**
237     Queued transactions and associated callbacks.
239     @property _queue
240     @type Object[]
241     @protected
242     @static
243     @since 3.5.0
244     **/
245     _queue: [],
247     // -- Public Methods -------------------------------------------------------
249     /**
250     Aborts the specified transaction.
252     This will cause the transaction's `onFailure` callback to be called and
253     will prevent any new script and link nodes from being added to the document,
254     but any resources that have already been requested will continue loading
255     (there's no safe way to prevent this, unfortunately).
257     *Note:* This method is deprecated as of 3.5.0, and will be removed in a
258     future version of YUI. Use the transaction-level `abort()` method instead.
260     @method abort
261     @param {Get.Transaction} transaction Transaction to abort.
262     @deprecated Use the `abort()` method on the transaction instead.
263     @static
264     **/
265     abort: function (transaction) {
266         var i, id, item, len, pending;
268         Y.log('`Y.Get.abort()` is deprecated as of 3.5.0. Use the `abort()` method on the transaction instead.', 'warn', 'get');
270         if (!transaction.abort) {
271             id          = transaction;
272             pending     = this._pending;
273             transaction = null;
275             if (pending && pending.transaction.id === id) {
276                 transaction   = pending.transaction;
277                 this._pending = null;
278             } else {
279                 for (i = 0, len = this._queue.length; i < len; ++i) {
280                     item = this._queue[i].transaction;
282                     if (item.id === id) {
283                         transaction = item;
284                         this._queue.splice(i, 1);
285                         break;
286                     }
287                 }
288             }
289         }
291         transaction && transaction.abort();
292     },
294     /**
295     Loads one or more CSS files.
297     The _urls_ parameter may be provided as a URL string, a request object,
298     or an array of URL strings and/or request objects.
300     A request object is just an object that contains a `url` property and zero
301     or more options that should apply specifically to that request.
302     Request-specific options take priority over transaction-level options and
303     default options.
305     URLs may be relative or absolute, and do not have to have the same origin
306     as the current page.
308     The `options` parameter may be omitted completely and a callback passed in
309     its place, if desired.
311     @example
313         // Load a single CSS file and log a message on completion.
314         Y.Get.css('foo.css', function (err) {
315             if (err) {
316                 Y.log('foo.css failed to load!');
317             } else {
318                 Y.log('foo.css was loaded successfully');
319             }
320         });
322         // Load multiple CSS files and log a message when all have finished
323         // loading.
324         var urls = ['foo.css', 'http://example.com/bar.css', 'baz/quux.css'];
326         Y.Get.css(urls, function (err) {
327             if (err) {
328                 Y.log('one or more files failed to load!');
329             } else {
330                 Y.log('all files loaded successfully');
331             }
332         });
334         // Specify transaction-level options, which will apply to all requests
335         // within the transaction.
336         Y.Get.css(urls, {
337             attributes: {'class': 'my-css'},
338             timeout   : 5000
339         });
341         // Specify per-request options, which override transaction-level and
342         // default options.
343         Y.Get.css([
344             {url: 'foo.css', attributes: {id: 'foo'}},
345             {url: 'bar.css', attributes: {id: 'bar', charset: 'iso-8859-1'}}
346         ]);
348     @method css
349     @param {String|Object|Array} urls URL string, request object, or array
350         of URLs and/or request objects to load.
351     @param {Object} [options] Options for this transaction. See the
352         `Y.Get.options` property for a complete list of available options.
353     @param {Function} [callback] Callback function to be called on completion.
354         This is a general callback and will be called before any more granular
355         callbacks (`onSuccess`, `onFailure`, etc.) specified in the `options`
356         object.
358         @param {Array|null} callback.err Array of errors that occurred during
359             the transaction, or `null` on success.
360         @param {Get.Transaction} callback.transaction Transaction object.
362     @return {Get.Transaction} Transaction object.
363     @static
364     **/
365     css: function (urls, options, callback) {
366         return this._load('css', urls, options, callback);
367     },
369     /**
370     Loads one or more JavaScript resources.
372     The _urls_ parameter may be provided as a URL string, a request object,
373     or an array of URL strings and/or request objects.
375     A request object is just an object that contains a `url` property and zero
376     or more options that should apply specifically to that request.
377     Request-specific options take priority over transaction-level options and
378     default options.
380     URLs may be relative or absolute, and do not have to have the same origin
381     as the current page.
383     The `options` parameter may be omitted completely and a callback passed in
384     its place, if desired.
386     Scripts will be executed in the order they're specified unless the `async`
387     option is `true`, in which case they'll be loaded in parallel and executed
388     in whatever order they finish loading.
390     @example
392         // Load a single JS file and log a message on completion.
393         Y.Get.js('foo.js', function (err) {
394             if (err) {
395                 Y.log('foo.js failed to load!');
396             } else {
397                 Y.log('foo.js was loaded successfully');
398             }
399         });
401         // Load multiple JS files, execute them in order, and log a message when
402         // all have finished loading.
403         var urls = ['foo.js', 'http://example.com/bar.js', 'baz/quux.js'];
405         Y.Get.js(urls, function (err) {
406             if (err) {
407                 Y.log('one or more files failed to load!');
408             } else {
409                 Y.log('all files loaded successfully');
410             }
411         });
413         // Specify transaction-level options, which will apply to all requests
414         // within the transaction.
415         Y.Get.js(urls, {
416             attributes: {'class': 'my-js'},
417             timeout   : 5000
418         });
420         // Specify per-request options, which override transaction-level and
421         // default options.
422         Y.Get.js([
423             {url: 'foo.js', attributes: {id: 'foo'}},
424             {url: 'bar.js', attributes: {id: 'bar', charset: 'iso-8859-1'}}
425         ]);
427     @method js
428     @param {String|Object|Array} urls URL string, request object, or array
429         of URLs and/or request objects to load.
430     @param {Object} [options] Options for this transaction. See the
431         `Y.Get.options` property for a complete list of available options.
432     @param {Function} [callback] Callback function to be called on completion.
433         This is a general callback and will be called before any more granular
434         callbacks (`onSuccess`, `onFailure`, etc.) specified in the `options`
435         object.
437         @param {Array|null} callback.err Array of errors that occurred during
438             the transaction, or `null` on success.
439         @param {Get.Transaction} callback.transaction Transaction object.
441     @return {Get.Transaction} Transaction object.
442     @since 3.5.0
443     @static
444     **/
445     js: function (urls, options, callback) {
446         return this._load('js', urls, options, callback);
447     },
449     /**
450     Loads one or more CSS and/or JavaScript resources in the same transaction.
452     Use this method when you want to load both CSS and JavaScript in a single
453     transaction and be notified when all requested URLs have finished loading,
454     regardless of type.
456     Behavior and options are the same as for the `css()` and `js()` methods. If
457     a resource type isn't specified in per-request options or transaction-level
458     options, Get will guess the file type based on the URL's extension (`.css`
459     or `.js`, with or without a following query string). If the file type can't
460     be guessed from the URL, a warning will be logged and Get will assume the
461     URL is a JavaScript resource.
463     @example
465         // Load both CSS and JS files in a single transaction, and log a message
466         // when all files have finished loading.
467         Y.Get.load(['foo.css', 'bar.js', 'baz.css'], function (err) {
468             if (err) {
469                 Y.log('one or more files failed to load!');
470             } else {
471                 Y.log('all files loaded successfully');
472             }
473         });
475     @method load
476     @param {String|Object|Array} urls URL string, request object, or array
477         of URLs and/or request objects to load.
478     @param {Object} [options] Options for this transaction. See the
479         `Y.Get.options` property for a complete list of available options.
480     @param {Function} [callback] Callback function to be called on completion.
481         This is a general callback and will be called before any more granular
482         callbacks (`onSuccess`, `onFailure`, etc.) specified in the `options`
483         object.
485         @param {Array|null} err Array of errors that occurred during the
486             transaction, or `null` on success.
487         @param {Get.Transaction} Transaction object.
489     @return {Get.Transaction} Transaction object.
490     @since 3.5.0
491     @static
492     **/
493     load: function (urls, options, callback) {
494         return this._load(null, urls, options, callback);
495     },
497     // -- Protected Methods ----------------------------------------------------
499     /**
500     Triggers an automatic purge if the purge threshold has been reached.
502     @method _autoPurge
503     @param {Number} threshold Purge threshold to use, in milliseconds.
504     @protected
505     @since 3.5.0
506     @static
507     **/
508     _autoPurge: function (threshold) {
509         if (threshold && this._purgeNodes.length >= threshold) {
510             Y.log('autopurge triggered after ' + this._purgeNodes.length + ' nodes', 'info', 'get');
511             this._purge(this._purgeNodes);
512         }
513     },
515     /**
516     Populates the `_env` property with information about the current
517     environment.
519     @method _getEnv
520     @return {Object} Environment information.
521     @protected
522     @since 3.5.0
523     @static
524     **/
525     _getEnv: function () {
526         var doc = Y.config.doc,
527             ua  = Y.UA;
529         // Note: some of these checks require browser sniffs since it's not
530         // feasible to load test files on every pageview just to perform a
531         // feature test. I'm sorry if this makes you sad.
532         return (this._env = {
534             // True if this is a browser that supports disabling async mode on
535             // dynamically created script nodes. See
536             // https://developer.mozilla.org/En/HTML/Element/Script#Attributes
538             // IE10 doesn't return true for the MDN feature test, so setting it explicitly,
539             // because it is async by default, and allows you to disable async by setting it to false
540             async: (doc && doc.createElement('script').async === true) || (ua.ie >= 10),
542             // True if this browser fires an event when a dynamically injected
543             // link node fails to load. This is currently true for Firefox 9+
544             // and WebKit 535.24+
545             cssFail: ua.gecko >= 9 || ua.compareVersions(ua.webkit, 535.24) >= 0,
547             // True if this browser fires an event when a dynamically injected
548             // link node finishes loading. This is currently true for IE, Opera,
549             // Firefox 9+, and WebKit 535.24+. Note that IE versions <9 fire the
550             // DOM 0 "onload" event, but not "load". All versions of IE fire
551             // "onload".
552             // davglass: Seems that Chrome on Android needs this to be false.
553             cssLoad: (
554                     (!ua.gecko && !ua.webkit) || ua.gecko >= 9 ||
555                     ua.compareVersions(ua.webkit, 535.24) >= 0
556                 ) && !(ua.chrome && ua.chrome <= 18),
558             // True if this browser preserves script execution order while
559             // loading scripts in parallel as long as the script node's `async`
560             // attribute is set to false to explicitly disable async execution.
561             preservesScriptOrder: !!(ua.gecko || ua.opera || (ua.ie && ua.ie >= 10))
562         });
563     },
565     _getTransaction: function (urls, options) {
566         var requests = [],
567             i, len, req, url;
569         if (!Lang.isArray(urls)) {
570             urls = [urls];
571         }
573         options = Y.merge(this.options, options);
575         // Clone the attributes object so we don't end up modifying it by ref.
576         options.attributes = Y.merge(this.options.attributes,
577                 options.attributes);
579         for (i = 0, len = urls.length; i < len; ++i) {
580             url = urls[i];
581             req = {attributes: {}};
583             // If `url` is a string, we create a URL object for it, then mix in
584             // global options and request-specific options. If it's an object
585             // with a "url" property, we assume it's a request object containing
586             // URL-specific options.
587             if (typeof url === 'string') {
588                 req.url = url;
589             } else if (url.url) {
590                 // URL-specific options override both global defaults and
591                 // request-specific options.
592                 Y.mix(req, url, false, null, 0, true);
593                 url = url.url; // Make url a string so we can use it later.
594             } else {
595                 Y.log('URL must be a string or an object with a `url` property.', 'error', 'get');
596                 continue;
597             }
599             Y.mix(req, options, false, null, 0, true);
601             // If we didn't get an explicit type for this URL either in the
602             // request options or the URL-specific options, try to determine
603             // one from the file extension.
604             if (!req.type) {
605                 if (this.REGEX_CSS.test(url)) {
606                     req.type = 'css';
607                 } else {
608                     if (!this.REGEX_JS.test(url)) {
609                         Y.log("Can't guess file type from URL. Assuming JS: " + url, 'warn', 'get');
610                     }
612                     req.type = 'js';
613                 }
614             }
616             // Mix in type-specific default options, but don't overwrite any
617             // options that have already been set.
618             Y.mix(req, req.type === 'js' ? this.jsOptions : this.cssOptions,
619                 false, null, 0, true);
621             // Give the node an id attribute if it doesn't already have one.
622             req.attributes.id || (req.attributes.id = Y.guid());
624             // Backcompat for <3.5.0 behavior.
625             if (req.win) {
626                 Y.log('The `win` option is deprecated as of 3.5.0. Use `doc` instead.', 'warn', 'get');
627                 req.doc = req.win.document;
628             } else {
629                 req.win = req.doc.defaultView || req.doc.parentWindow;
630             }
632             if (req.charset) {
633                 Y.log('The `charset` option is deprecated as of 3.5.0. Set `attributes.charset` instead.', 'warn', 'get');
634                 req.attributes.charset = req.charset;
635             }
637             requests.push(req);
638         }
640         return new Transaction(requests, options);
641     },
643     _load: function (type, urls, options, callback) {
644         var transaction;
646         // Allow callback as third param.
647         if (typeof options === 'function') {
648             callback = options;
649             options  = {};
650         }
652         options || (options = {});
653         options.type = type;
655         options._onFinish = Get._onTransactionFinish;
657         if (!this._env) {
658             this._getEnv();
659         }
661         transaction = this._getTransaction(urls, options);
663         this._queue.push({
664             callback   : callback,
665             transaction: transaction
666         });
668         this._next();
670         return transaction;
671     },
673     _onTransactionFinish : function() {
674         Get._pending = null;
675         Get._next();
676     },
678     _next: function () {
679         var item;
681         if (this._pending) {
682             return;
683         }
685         item = this._queue.shift();
687         if (item) {
688             this._pending = item;
689             item.transaction.execute(item.callback);
690         }
691     },
693     _purge: function (nodes) {
694         var purgeNodes    = this._purgeNodes,
695             isTransaction = nodes !== purgeNodes,
696             index, node;
698         while (node = nodes.pop()) { // assignment
699             // Don't purge nodes that haven't finished loading (or errored out),
700             // since this can hang the transaction.
701             if (!node._yuiget_finished) {
702                 continue;
703             }
705             node.parentNode && node.parentNode.removeChild(node);
707             // If this is a transaction-level purge and this node also exists in
708             // the Get-level _purgeNodes array, we need to remove it from
709             // _purgeNodes to avoid creating a memory leak. The indexOf lookup
710             // sucks, but until we get WeakMaps, this is the least troublesome
711             // way to do this (we can't just hold onto node ids because they may
712             // not be in the same document).
713             if (isTransaction) {
714                 index = Y.Array.indexOf(purgeNodes, node);
716                 if (index > -1) {
717                     purgeNodes.splice(index, 1);
718                 }
719             }
720         }
721     }
725 Alias for `js()`.
727 @method script
728 @static
730 Get.script = Get.js;
733 Represents a Get transaction, which may contain requests for one or more JS or
734 CSS files.
736 This class should not be instantiated manually. Instances will be created and
737 returned as needed by Y.Get's `css()`, `js()`, and `load()` methods.
739 @class Get.Transaction
740 @constructor
741 @since 3.5.0
743 Get.Transaction = Transaction = function (requests, options) {
744     var self = this;
746     self.id       = Transaction._lastId += 1;
747     self.data     = options.data;
748     self.errors   = [];
749     self.nodes    = [];
750     self.options  = options;
751     self.requests = requests;
753     self._callbacks = []; // callbacks to call after execution finishes
754     self._queue     = [];
755     self._reqsWaiting   = 0;
757     // Deprecated pre-3.5.0 properties.
758     self.tId = self.id; // Use `id` instead.
759     self.win = options.win || Y.config.win;
763 Arbitrary data object associated with this transaction.
765 This object comes from the options passed to `Get.css()`, `Get.js()`, or
766 `Get.load()`, and will be `undefined` if no data object was specified.
768 @property {Object} data
772 Array of errors that have occurred during this transaction, if any.
774 @since 3.5.0
775 @property {Object[]} errors
776 @property {String} errors.error Error message.
777 @property {Object} errors.request Request object related to the error.
781 Numeric id for this transaction, unique among all transactions within the same
782 YUI sandbox in the current pageview.
784 @property {Number} id
785 @since 3.5.0
789 HTMLElement nodes (native ones, not YUI Node instances) that have been inserted
790 during the current transaction.
792 @property {HTMLElement[]} nodes
796 Options associated with this transaction.
798 See `Get.options` for the full list of available options.
800 @property {Object} options
801 @since 3.5.0
805 Request objects contained in this transaction. Each request object represents
806 one CSS or JS URL that will be (or has been) requested and loaded into the page.
808 @property {Object} requests
809 @since 3.5.0
813 Id of the most recent transaction.
815 @property _lastId
816 @type Number
817 @protected
818 @static
820 Transaction._lastId = 0;
822 Transaction.prototype = {
823     // -- Public Properties ----------------------------------------------------
825     /**
826     Current state of this transaction. One of "new", "executing", or "done".
828     @property _state
829     @type String
830     @protected
831     **/
832     _state: 'new', // "new", "executing", or "done"
834     // -- Public Methods -------------------------------------------------------
836     /**
837     Aborts this transaction.
839     This will cause the transaction's `onFailure` callback to be called and
840     will prevent any new script and link nodes from being added to the document,
841     but any resources that have already been requested will continue loading
842     (there's no safe way to prevent this, unfortunately).
844     @method abort
845     @param {String} [msg="Aborted."] Optional message to use in the `errors`
846         array describing why the transaction was aborted.
847     **/
848     abort: function (msg) {
849         this._pending    = null;
850         this._pendingCSS = null;
851         this._pollTimer  = clearTimeout(this._pollTimer);
852         this._queue      = [];
853         this._reqsWaiting    = 0;
855         this.errors.push({error: msg || 'Aborted'});
856         this._finish();
857     },
859     /**
860     Begins execting the transaction.
862     There's usually no reason to call this manually, since Get will call it
863     automatically when other pending transactions have finished. If you really
864     want to execute your transaction before Get does, you can, but be aware that
865     this transaction's scripts may end up executing before the scripts in other
866     pending transactions.
868     If the transaction is already executing, the specified callback (if any)
869     will be queued and called after execution finishes. If the transaction has
870     already finished, the callback will be called immediately (the transaction
871     will not be executed again).
873     @method execute
874     @param {Function} callback Callback function to execute after all requests
875         in the transaction are complete, or after the transaction is aborted.
876     **/
877     execute: function (callback) {
878         var self     = this,
879             requests = self.requests,
880             state    = self._state,
881             i, len, queue, req;
883         if (state === 'done') {
884             callback && callback(self.errors.length ? self.errors : null, self);
885             return;
886         } else {
887             callback && self._callbacks.push(callback);
889             if (state === 'executing') {
890                 return;
891             }
892         }
894         self._state = 'executing';
895         self._queue = queue = [];
897         if (self.options.timeout) {
898             self._timeout = setTimeout(function () {
899                 self.abort('Timeout');
900             }, self.options.timeout);
901         }
903         self._reqsWaiting = requests.length;
905         for (i = 0, len = requests.length; i < len; ++i) {
906             req = requests[i];
908             if (req.async || req.type === 'css') {
909                 // No need to queue CSS or fully async JS.
910                 self._insert(req);
911             } else {
912                 queue.push(req);
913             }
914         }
916         self._next();
917     },
919     /**
920     Manually purges any `<script>` or `<link>` nodes this transaction has
921     created.
923     Be careful when purging a transaction that contains CSS requests, since
924     removing `<link>` nodes will also remove any styles they applied.
926     @method purge
927     **/
928     purge: function () {
929         Get._purge(this.nodes);
930     },
932     // -- Protected Methods ----------------------------------------------------
933     _createNode: function (name, attrs, doc) {
934         var node = doc.createElement(name),
935             attr, testEl;
937         if (!CUSTOM_ATTRS) {
938             // IE6 and IE7 expect property names rather than attribute names for
939             // certain attributes. Rather than sniffing, we do a quick feature
940             // test the first time _createNode() runs to determine whether we
941             // need to provide a workaround.
942             testEl = doc.createElement('div');
943             testEl.setAttribute('class', 'a');
945             CUSTOM_ATTRS = testEl.className === 'a' ? {} : {
946                 'for'  : 'htmlFor',
947                 'class': 'className'
948             };
949         }
951         for (attr in attrs) {
952             if (attrs.hasOwnProperty(attr)) {
953                 node.setAttribute(CUSTOM_ATTRS[attr] || attr, attrs[attr]);
954             }
955         }
957         return node;
958     },
960     _finish: function () {
961         var errors  = this.errors.length ? this.errors : null,
962             options = this.options,
963             thisObj = options.context || this,
964             data, i, len;
966         if (this._state === 'done') {
967             return;
968         }
970         this._state = 'done';
972         for (i = 0, len = this._callbacks.length; i < len; ++i) {
973             this._callbacks[i].call(thisObj, errors, this);
974         }
976         data = this._getEventData();
978         if (errors) {
979             if (options.onTimeout && errors[errors.length - 1].error === 'Timeout') {
980                 options.onTimeout.call(thisObj, data);
981             }
983             if (options.onFailure) {
984                 options.onFailure.call(thisObj, data);
985             }
986         } else if (options.onSuccess) {
987             options.onSuccess.call(thisObj, data);
988         }
990         if (options.onEnd) {
991             options.onEnd.call(thisObj, data);
992         }
994         if (options._onFinish) {
995             options._onFinish();
996         }
997     },
999     _getEventData: function (req) {
1000         if (req) {
1001             // This merge is necessary for backcompat. I hate it.
1002             return Y.merge(this, {
1003                 abort  : this.abort, // have to copy these because the prototype isn't preserved
1004                 purge  : this.purge,
1005                 request: req,
1006                 url    : req.url,
1007                 win    : req.win
1008             });
1009         } else {
1010             return this;
1011         }
1012     },
1014     _getInsertBefore: function (req) {
1015         var doc = req.doc,
1016             el  = req.insertBefore,
1017             cache, docStamp;
1019         if (el) {
1020             return typeof el === 'string' ? doc.getElementById(el) : el;
1021         }
1023         cache    = Get._insertCache;
1024         docStamp = Y.stamp(doc);
1026         if ((el = cache[docStamp])) { // assignment
1027             return el;
1028         }
1030         // Inserting before a <base> tag apparently works around an IE bug
1031         // (according to a comment from pre-3.5.0 Y.Get), but I'm not sure what
1032         // bug that is, exactly. Better safe than sorry?
1033         if ((el = doc.getElementsByTagName('base')[0])) { // assignment
1034             return (cache[docStamp] = el);
1035         }
1037         // Look for a <head> element.
1038         el = doc.head || doc.getElementsByTagName('head')[0];
1040         if (el) {
1041             // Create a marker node at the end of <head> to use as an insertion
1042             // point. Inserting before this node will ensure that all our CSS
1043             // gets inserted in the correct order, to maintain style precedence.
1044             el.appendChild(doc.createTextNode(''));
1045             return (cache[docStamp] = el.lastChild);
1046         }
1048         // If all else fails, just insert before the first script node on the
1049         // page, which is virtually guaranteed to exist.
1050         return (cache[docStamp] = doc.getElementsByTagName('script')[0]);
1051     },
1053     _insert: function (req) {
1054         var env          = Get._env,
1055             insertBefore = this._getInsertBefore(req),
1056             isScript     = req.type === 'js',
1057             node         = req.node,
1058             self         = this,
1059             ua           = Y.UA,
1060             cssTimeout, nodeType;
1062         if (!node) {
1063             if (isScript) {
1064                 nodeType = 'script';
1065             } else if (!env.cssLoad && ua.gecko) {
1066                 nodeType = 'style';
1067             } else {
1068                 nodeType = 'link';
1069             }
1071             node = req.node = this._createNode(nodeType, req.attributes,
1072                 req.doc);
1073         }
1075         function onError() {
1076             self._progress('Failed to load ' + req.url, req);
1077         }
1079         function onLoad() {
1080             if (cssTimeout) {
1081                 clearTimeout(cssTimeout);
1082             }
1084             self._progress(null, req);
1085         }
1087         // Deal with script asynchronicity.
1088         if (isScript) {
1089             node.setAttribute('src', req.url);
1091             if (req.async) {
1092                 // Explicitly indicate that we want the browser to execute this
1093                 // script asynchronously. This is necessary for older browsers
1094                 // like Firefox <4.
1095                 node.async = true;
1096             } else {
1097                 if (env.async) {
1098                     // This browser treats injected scripts as async by default
1099                     // (standard HTML5 behavior) but asynchronous loading isn't
1100                     // desired, so tell the browser not to mark this script as
1101                     // async.
1102                     node.async = false;
1103                 }
1105                 // If this browser doesn't preserve script execution order based
1106                 // on insertion order, we'll need to avoid inserting other
1107                 // scripts until this one finishes loading.
1108                 if (!env.preservesScriptOrder) {
1109                     this._pending = req;
1110                 }
1111             }
1112         } else {
1113             if (!env.cssLoad && ua.gecko) {
1114                 // In Firefox <9, we can import the requested URL into a <style>
1115                 // node and poll for the existence of node.sheet.cssRules. This
1116                 // gives us a reliable way to determine CSS load completion that
1117                 // also works for cross-domain stylesheets.
1118                 //
1119                 // Props to Zach Leatherman for calling my attention to this
1120                 // technique.
1121                 node.innerHTML = (req.attributes.charset ?
1122                     '@charset "' + req.attributes.charset + '";' : '') +
1123                     '@import "' + req.url + '";';
1124             } else {
1125                 node.setAttribute('href', req.url);
1126             }
1127         }
1129         // Inject the node.
1130         if (isScript && ua.ie && (ua.ie < 9 || (document.documentMode && document.documentMode < 9))) {
1131             // Script on IE < 9, and IE 9+ when in IE 8 or older modes, including quirks mode.
1132             node.onreadystatechange = function () {
1133                 if (/loaded|complete/.test(node.readyState)) {
1134                     node.onreadystatechange = null;
1135                     onLoad();
1136                 }
1137             };
1138         } else if (!isScript && !env.cssLoad) {
1139             // CSS on Firefox <9 or WebKit.
1140             this._poll(req);
1141         } else {
1142             // Script or CSS on everything else. Using DOM 0 events because that
1143             // evens the playing field with older IEs.
1145             if (ua.ie >= 10) {
1147                 // We currently need to introduce a timeout for IE10, since it
1148                 // calls onerror/onload synchronously for 304s - messing up existing
1149                 // program flow.
1151                 // Remove this block if the following bug gets fixed by GA
1152                 /*jshint maxlen: 1500 */
1153                 // https://connect.microsoft.com/IE/feedback/details/763871/dynamically-loaded-scripts-with-304s-responses-interrupt-the-currently-executing-js-thread-onload
1154                 node.onerror = function() { setTimeout(onError, 0); };
1155                 node.onload  = function() { setTimeout(onLoad, 0); };
1156             } else {
1157                 node.onerror = onError;
1158                 node.onload  = onLoad;
1159             }
1161             // If this browser doesn't fire an event when CSS fails to load,
1162             // fail after a timeout to avoid blocking the transaction queue.
1163             if (!env.cssFail && !isScript) {
1164                 cssTimeout = setTimeout(onError, req.timeout || 3000);
1165             }
1166         }
1168         this.nodes.push(node);
1169         insertBefore.parentNode.insertBefore(node, insertBefore);
1170     },
1172     _next: function () {
1173         if (this._pending) {
1174             return;
1175         }
1177         // If there are requests in the queue, insert the next queued request.
1178         // Otherwise, if we're waiting on already-inserted requests to finish,
1179         // wait longer. If there are no queued requests and we're not waiting
1180         // for anything to load, then we're done!
1181         if (this._queue.length) {
1182             this._insert(this._queue.shift());
1183         } else if (!this._reqsWaiting) {
1184             this._finish();
1185         }
1186     },
1188     _poll: function (newReq) {
1189         var self       = this,
1190             pendingCSS = self._pendingCSS,
1191             isWebKit   = Y.UA.webkit,
1192             i, hasRules, j, nodeHref, req, sheets;
1194         if (newReq) {
1195             pendingCSS || (pendingCSS = self._pendingCSS = []);
1196             pendingCSS.push(newReq);
1198             if (self._pollTimer) {
1199                 // A poll timeout is already pending, so no need to create a
1200                 // new one.
1201                 return;
1202             }
1203         }
1205         self._pollTimer = null;
1207         // Note: in both the WebKit and Gecko hacks below, a CSS URL that 404s
1208         // will still be treated as a success. There's no good workaround for
1209         // this.
1211         for (i = 0; i < pendingCSS.length; ++i) {
1212             req = pendingCSS[i];
1214             if (isWebKit) {
1215                 // Look for a stylesheet matching the pending URL.
1216                 sheets   = req.doc.styleSheets;
1217                 j        = sheets.length;
1218                 nodeHref = req.node.href;
1220                 while (--j >= 0) {
1221                     if (sheets[j].href === nodeHref) {
1222                         pendingCSS.splice(i, 1);
1223                         i -= 1;
1224                         self._progress(null, req);
1225                         break;
1226                     }
1227                 }
1228             } else {
1229                 // Many thanks to Zach Leatherman for calling my attention to
1230                 // the @import-based cross-domain technique used here, and to
1231                 // Oleg Slobodskoi for an earlier same-domain implementation.
1232                 //
1233                 // See Zach's blog for more details:
1234                 // http://www.zachleat.com/web/2010/07/29/load-css-dynamically/
1235                 try {
1236                     // We don't really need to store this value since we never
1237                     // use it again, but if we don't store it, Closure Compiler
1238                     // assumes the code is useless and removes it.
1239                     hasRules = !!req.node.sheet.cssRules;
1241                     // If we get here, the stylesheet has loaded.
1242                     pendingCSS.splice(i, 1);
1243                     i -= 1;
1244                     self._progress(null, req);
1245                 } catch (ex) {
1246                     // An exception means the stylesheet is still loading.
1247                 }
1248             }
1249         }
1251         if (pendingCSS.length) {
1252             self._pollTimer = setTimeout(function () {
1253                 self._poll.call(self);
1254             }, self.options.pollInterval);
1255         }
1256     },
1258     _progress: function (err, req) {
1259         var options = this.options;
1261         if (err) {
1262             req.error = err;
1264             this.errors.push({
1265                 error  : err,
1266                 request: req
1267             });
1269             Y.log(err, 'error', 'get');
1270         }
1272         req.node._yuiget_finished = req.finished = true;
1274         if (options.onProgress) {
1275             options.onProgress.call(options.context || this,
1276                 this._getEventData(req));
1277         }
1279         if (req.autopurge) {
1280             // Pre-3.5.0 Get always excludes the most recent node from an
1281             // autopurge. I find this odd, but I'm keeping that behavior for
1282             // the sake of backcompat.
1283             Get._autoPurge(this.options.purgethreshold);
1284             Get._purgeNodes.push(req.node);
1285         }
1287         if (this._pending === req) {
1288             this._pending = null;
1289         }
1291         this._reqsWaiting -= 1;
1293         this._next();
1294     }
1298 }, '3.13.0', {"requires": ["yui-base"]});