in harness/curl.js [13:1367]
(function (global) {
//"use strict"; don't restore this until the config routine is refactored
var
version = '0.8.11',
curlName = 'curl',
defineName = 'define',
bootScriptAttr = 'data-curl-run',
bootScript,
userCfg,
prevCurl,
prevDefine,
doc = global.document,
head = doc && (doc['head'] || doc.getElementsByTagName('head')[0]),
// to keep IE from crying, we need to put scripts before any
// <base> elements, but after any <meta>. this should do it:
insertBeforeEl = head && head.getElementsByTagName('base')[0] || null,
// constants / flags
msgUsingExports = {},
msgFactoryExecuted = {},
// this is the list of scripts that IE is loading. one of these will
// be the "interactive" script. too bad IE doesn't send a readystatechange
// event to tell us exactly which one.
activeScripts = {},
// readyStates for IE6-9
readyStates = 'addEventListener' in global ? {} : { 'loaded': 1, 'complete': 1 },
// these are always handy :)
cleanPrototype = {},
toString = cleanPrototype.toString,
undef,
// local cache of resource definitions (lightweight promises)
cache = {},
// local url cache
urlCache = {},
// preload are files that must be loaded before any others
preload = false,
// net to catch anonymous define calls' arguments (non-IE browsers)
argsNet,
// RegExp's used later, pre-compiled here
dontAddExtRx = /\?|\.js\b/,
absUrlRx = /^\/|^[^:]+:\/\/|^[A-Za-z]:[\\/]/,
findDotsRx = /(\.)(\.?)(?:$|\/([^\.\/]+.*)?)/g,
removeCommentsRx = /\/\*[\s\S]*?\*\/|\/\/.*?[\n\r]/g,
findRValueRequiresRx = /require\s*\(\s*(["'])(.*?[^\\])\1\s*\)|[^\\]?(["'])/g,
splitCommaSepRx = /\s*,\s*/,
cjsGetters,
core;
function noop () {}
function isType (obj, type) {
return toString.call(obj).indexOf('[object ' + type) == 0;
}
function normalizePkgDescriptor (descriptor, isPkg) {
var main;
descriptor.path = removeEndSlash(descriptor['path'] || descriptor['location'] || '');
if (isPkg) {
main = descriptor['main'] || './main';
if (!isRelUrl(main)) main = './' + main;
// trailing slashes trick reduceLeadingDots to see them as base ids
descriptor.main = reduceLeadingDots(main, descriptor.name + '/');
}
descriptor.config = descriptor['config'];
return descriptor;
}
function isRelUrl (it) {
return it.charAt(0) == '.';
}
function isAbsUrl (it) {
return absUrlRx.test(it);
}
function joinPath (path, file) {
return removeEndSlash(path) + '/' + file;
}
function removeEndSlash (path) {
return path && path.charAt(path.length - 1) == '/' ? path.substr(0, path.length - 1) : path;
}
function reduceLeadingDots (childId, baseId) {
// this algorithm is similar to dojo's compactPath, which interprets
// module ids of "." and ".." as meaning "grab the module whose name is
// the same as my folder or parent folder". These special module ids
// are not included in the AMD spec but seem to work in node.js, too.
var removeLevels, normId, levels, isRelative, diff;
removeLevels = 1;
normId = childId;
// remove leading dots and count levels
if (isRelUrl(normId)) {
isRelative = true;
normId = normId.replace(findDotsRx, function (m, dot, doubleDot, remainder) {
if (doubleDot) removeLevels++;
return remainder || '';
});
}
if (isRelative) {
levels = baseId.split('/');
diff = levels.length - removeLevels;
if (diff < 0) {
// this is an attempt to navigate above parent module.
// maybe dev wants a url or something. punt and return url;
return childId;
}
levels.splice(diff, removeLevels);
// normId || [] prevents concat from adding extra "/" when
// normId is reduced to a blank string
return levels.concat(normId || []).join('/');
}
else {
return normId;
}
}
function pluginParts (id) {
var delPos = id.indexOf('!');
return {
resourceId: id.substr(delPos + 1),
// resourceId can be zero length
pluginId: delPos >= 0 && id.substr(0, delPos)
};
}
function Begetter () {}
function beget (parent, mixin) {
Begetter.prototype = parent || cleanPrototype;
var child = new Begetter();
Begetter.prototype = cleanPrototype;
for (var p in mixin) child[p] = mixin[p];
return child;
}
function Promise () {
var self, thens, complete;
self = this;
thens = [];
function then (resolved, rejected, progressed) {
// capture calls to callbacks
thens.push([resolved, rejected, progressed]);
}
function notify (which, arg) {
// complete all callbacks
var aThen, cb, i = 0;
while ((aThen = thens[i++])) {
cb = aThen[which];
if (cb) cb(arg);
}
}
complete = function promiseComplete (success, arg) {
// switch over to sync then()
then = success ?
function (resolved, rejected) { resolved && resolved(arg); } :
function (resolved, rejected) { rejected && rejected(arg); };
// we no longer throw during multiple calls to resolve or reject
// since we don't really provide useful information anyways.
complete = noop;
// complete all callbacks
notify(success ? 0 : 1, arg);
// no more notifications
notify = noop;
// release memory
thens = undef;
};
this.then = function (resolved, rejected, progressed) {
then(resolved, rejected, progressed);
return self;
};
this.resolve = function (val) {
self.resolved = val;
complete(true, val);
};
this.reject = function (ex) {
self.rejected = ex;
complete(false, ex);
};
this.progress = function (msg) {
notify(2, msg);
}
}
function isPromise (o) {
return o instanceof Promise || o instanceof CurlApi;
}
function when (promiseOrValue, callback, errback, progback) {
// we can't just sniff for then(). if we do, resources that have a
// then() method will make dependencies wait!
if (isPromise(promiseOrValue)) {
return promiseOrValue.then(callback, errback, progback);
}
else {
return callback(promiseOrValue);
}
}
/**
* Returns a function that when executed, executes a lambda function,
* but only executes it the number of times stated by howMany.
* When done executing, it executes the completed function. Each callback
* function receives the same parameters that are supplied to the
* returned function each time it executes. In other words, they
* are passed through.
* @private
* @param howMany {Number} must be greater than zero
* @param lambda {Function} executed each time
* @param completed {Function} only executes once when the counter
* reaches zero
* @returns {Function}
*/
function countdown (howMany, lambda, completed) {
var result;
return function () {
if (--howMany >= 0 && lambda) result = lambda.apply(undef, arguments);
// we want ==, not <=, since some callers expect call-once functionality
if (howMany == 0 && completed) completed(result);
return result;
}
}
core = {
/**
* * reduceLeadingDots of id against parentId
* - if there are too many dots (path goes beyond parent), it's a url
* - return reduceLeadingDots of id against baseUrl + parentId;
* * if id is a url (starts with dots or slash or protocol)
* - pathInfo = { config: userCfg, url: url }
* * if not a url, id-to-id transform here.
* - main module expansion
* - plugin prefix expansion
* - coordinate main module expansion with plugin expansion
* - main module expansion happens first
* - future: other transforms?
* @param id
* @param parentId
* @param cfg
* @return {*}
*/
toAbsId: function (id, parentId, cfg) {
var absId, pluginId, parts;
absId = reduceLeadingDots(id, parentId);
// if this is still a relative path, it must be a url
// so just punt, otherwise...
if (isRelUrl(absId)) return absId;
// plugin id split
parts = pluginParts(absId);
pluginId = parts.pluginId;
absId = pluginId || parts.resourceId;
// main id expansion
if (absId in cfg.pathMap) {
absId = cfg.pathMap[absId].main || absId;
}
// plugin id expansion
if (pluginId) {
if (pluginId.indexOf('/') < 0 && !(pluginId in cfg.pathMap)) {
absId = joinPath(cfg.pluginPath, pluginId);
}
absId = absId + '!' + parts.resourceId;
}
return absId;
},
createContext: function (cfg, baseId, depNames, isPreload) {
var def;
def = new Promise();
def.id = baseId || ''; // '' == global
def.isPreload = isPreload;
def.depNames = depNames;
def.config = cfg;
// functions that dependencies will use:
function toAbsId (childId, checkPlugins) {
var absId, parts, plugin;
absId = core.toAbsId(childId, def.id, cfg);
if (!checkPlugins) return absId;
parts = pluginParts(absId);
if (!parts.pluginId) return absId;
plugin = cache[parts.pluginId];
// check if plugin supports the normalize method
if ('normalize' in plugin) {
// note: dojo/has may return falsey values (0, actually)
parts.resourceId = plugin['normalize'](parts.resourceId, toAbsId, def.config) || '';
}
else {
parts.resourceId = toAbsId(parts.resourceId);
}
return parts.pluginId + '!' + parts.resourceId;
}
function toUrl (n) {
// the AMD spec states that we should not append an extension
// in this function since it could already be appended.
// we need to use toAbsId in case this is a module id.
return core.resolvePathInfo(toAbsId(n, true), cfg).url;
}
function localRequire (ids, callback, errback) {
var cb, rvid, childDef, earlyExport;
// this is public, so send pure function
// also fixes issue #41
cb = callback && function () { callback.apply(undef, arguments[0]); };
// RValue require (CommonJS)
if (isType(ids, 'String')) {
if (cb) {
throw new Error('require(id, callback) not allowed');
}
// return resource
rvid = toAbsId(ids, true);
childDef = cache[rvid];
if (!(rvid in cache)) {
// this should only happen when devs attempt their own
// manual wrapping of cjs modules or get confused with
// the callback syntax:
throw new Error('Module not resolved: ' + rvid);
}
earlyExport = isPromise(childDef) && childDef.exports;
return earlyExport || childDef;
}
else {
when(core.getDeps(core.createContext(cfg, def.id, ids, isPreload)), cb, errback);
}
}
def.require = localRequire;
localRequire['toUrl'] = toUrl;
def.toAbsId = toAbsId;
return def;
},
createResourceDef: function (cfg, id, isPreload) {
var def, origResolve, execute;
def = core.createContext(cfg, id, undef, isPreload);
origResolve = def.resolve;
// using countdown to only execute definition function once
execute = countdown(1, function (deps) {
def.deps = deps;
try {
return core.executeDefFunc(def);
}
catch (ex) {
def.reject(ex);
}
});
// intercept resolve function to execute definition function
// before resolving
def.resolve = function resolve (deps) {
when(isPreload || preload, function () {
origResolve((cache[def.id] = urlCache[def.url] = execute(deps)));
});
};
// track exports
def.exportsReady = function executeFactory (deps) {
when(isPreload || preload, function () {
// only resolve early if we also use exports (to avoid
// circular dependencies). def.exports will have already
// been set by the getDeps loop before we get here.
if (def.exports) {
execute(deps);
def.progress(msgFactoryExecuted);
}
});
};
return def;
},
createPluginDef: function (cfg, id, resId, isPreload) {
var def;
// use resource id for local require and toAbsId
def = core.createContext(cfg, resId, undef, isPreload);
return def;
},
getCjsRequire: function (def) {
return def.require;
},
getCjsExports: function (def) {
return def.exports || (def.exports = {});
},
getCjsModule: function (def) {
var module = def.module;
if (!module) {
module = def.module = {
'id': def.id,
'uri': core.getDefUrl(def),
'exports': core.getCjsExports(def),
'config': function () { return def.config; }
};
module.exports = module['exports']; // oh closure compiler!
}
return module;
},
getDefUrl: function (def) {
// note: this is used by cjs module.uri
return def.url || (def.url = core.checkToAddJsExt(def.require['toUrl'](def.id), def.config));
},
/**
* Sets the curl() and define() APIs.
* @param [cfg] {Object|Null} set of config params. If missing or null,
* this function will set the default API!
*/
setApi: function (cfg) {
/*
scenarios:
1. global config sets apiName: "require"
- first call to config sets api
- second and later calls are ignored
- prevCurl cannot exist
2. no global config, first call to config() sets api
- first call to config has no api info
- second call to config sets api
- third and later calls must be ignored
3. global config that doesn't set api, first call does though
- same as #2
4. api info is never set
- how to know when to stop ignoring?
objectives:
1. fail before mistakenly overwriting global[curlName]
2. allow rename/relocate of curl() and define()
3. restore curl() if we overwrote it
*/
var apiName, defName, apiObj, defObj,
failMsg, okToOverwrite;
apiName = curlName;
defName = defineName;
apiObj = defObj = global;
failMsg = ' already exists';
// if we're not setting defaults
if (cfg) {
// is it ok to overwrite existing api functions?
okToOverwrite = cfg['overwriteApi'] || cfg.overwriteApi;
// allow dev to rename/relocate curl() to another object
apiName = cfg['apiName'] || cfg.apiName || apiName;
apiObj = cfg['apiContext'] || cfg.apiContext || apiObj;
// define() too
defName = cfg['defineName'] || cfg.defineName || defName;
defObj = cfg['defineContext'] || cfg.defineContext || defObj;
// curl() already existed, restore it if this is not a
// setDefaults pass. dev must be a good citizen and set
// apiName/apiContext (see below).
if (prevCurl && isType(prevCurl, 'Function')) {
// restore previous curl()
global[curlName] = prevCurl;
}
prevCurl = null; // don't check ever again
// ditto for define()
if (prevDefine && isType(prevDefine, 'Function')) {
// restore previous curl()
global[defineName] = prevDefine;
}
prevDefine = null; // don't check ever again
// check if we're mistakenly overwriting either api
// if we're configuring, and there's a curl(), and it's not
// ours -- and we're not explicitly overwriting -- throw!
// Note: if we're setting defaults, we *must* overwrite curl
// so that dev can configure it. This is no different than
// noConflict()-type methods.
if (!okToOverwrite) {
if (apiObj[apiName] && apiObj[apiName] != _curl) {
throw new Error(apiName + failMsg);
}
// check if we're overwriting amd api
if (defObj[defName] && defObj[defName] != define) {
throw new Error(defName + failMsg);
}
}
}
// set curl api
apiObj[apiName] = _curl;
// set AMD public api: define()
defObj[defName] = define;
},
config: function (cfg) {
var prevCfg, newCfg, pluginCfgs, p;
// convert from closure-safe names
if ('baseUrl' in cfg) cfg.baseUrl = cfg['baseUrl'];
if ('main' in cfg) cfg.main = cfg['main'];
if ('preloads' in cfg) cfg.preloads = cfg['preloads'];
if ('pluginPath' in cfg) cfg.pluginPath = cfg['pluginPath'];
if ('dontAddFileExt' in cfg || cfg.dontAddFileExt) {
cfg.dontAddFileExt = new RegExp(cfg['dontAddFileExt'] || cfg.dontAddFileExt);
}
prevCfg = userCfg;
newCfg = beget(prevCfg, cfg);
// create object to hold path map.
// each plugin and package will have its own pathMap, too.
newCfg.pathMap = beget(prevCfg.pathMap);
pluginCfgs = cfg['plugins'] || {};
newCfg.plugins = beget(prevCfg.plugins);
newCfg.paths = beget(prevCfg.paths, cfg.paths);
newCfg.packages = beget(prevCfg.packages, cfg.packages);
// temporary arrays of paths. this will be converted to
// a regexp for fast path parsing.
newCfg.pathList = [];
// normalizes path/package info and places info on either
// the global cfg.pathMap or on a plugin-specific altCfg.pathMap.
// also populates a pathList on cfg or plugin configs.
function fixAndPushPaths (coll, isPkg) {
var id, pluginId, data, parts, currCfg, info;
for (var name in coll) {
data = coll[name];
if (isType(data, 'String')) data = {
path: coll[name]
};
// grab the package id, if specified. default to
// property name, if missing.
data.name = data.name || name;
currCfg = newCfg;
// check if this is a plugin-specific path
parts = pluginParts(removeEndSlash(data.name));
id = parts.resourceId;
pluginId = parts.pluginId;
if (pluginId) {
// plugin-specific path
currCfg = pluginCfgs[pluginId];
if (!currCfg) {
currCfg = pluginCfgs[pluginId] = beget(newCfg);
currCfg.pathMap = beget(newCfg.pathMap);
currCfg.pathList = [];
}
// remove plugin-specific path from coll
delete coll[name];
}
info = normalizePkgDescriptor(data, isPkg);
if (info.config) info.config = beget(newCfg, info.config);
info.specificity = id.split('/').length;
if (id) {
currCfg.pathMap[id] = info;
currCfg.pathList.push(id);
}
else {
// naked plugin name signifies baseUrl for plugin
// resources. baseUrl could be relative to global
// baseUrl.
currCfg.baseUrl = core.resolveUrl(data.path, newCfg);
}
}
}
// adds the path matching regexp onto the cfg or plugin cfgs.
function convertPathMatcher (cfg) {
var pathMap = cfg.pathMap;
cfg.pathRx = new RegExp('^(' +
cfg.pathList.sort(function (a, b) { return pathMap[b].specificity - pathMap[a].specificity; } )
.join('|')
.replace(/\/|\./g, '\\$&') +
')(?=\\/|$)'
);
delete cfg.pathList;
}
// fix all new packages, then paths (in case there are
// plugin-specific paths for a main module, such as wire!)
fixAndPushPaths(cfg['packages'], true);
fixAndPushPaths(cfg['paths'], false);
// process plugins after packages in case we already perform an
// id transform on a plugin (i.e. it's a package.main)
for (p in pluginCfgs) {
var absId = core.toAbsId(p + '!', '', newCfg);
newCfg.plugins[absId.substr(0, absId.length - 1)] = pluginCfgs[p];
}
pluginCfgs = newCfg.plugins;
// create search regex for each path map
for (p in pluginCfgs) {
// inherit full config
pluginCfgs[p] = beget(newCfg, pluginCfgs[p]);
var pathList = pluginCfgs[p].pathList;
if (pathList) {
pluginCfgs[p].pathList = pathList.concat(newCfg.pathList);
convertPathMatcher(pluginCfgs[p]);
}
}
// ugh, this is ugly, but necessary until we refactor this function
// copy previous pathMap items onto pathList
for (p in prevCfg.pathMap) {
if (!newCfg.pathMap.hasOwnProperty(p)) newCfg.pathList.push(p);
}
convertPathMatcher(newCfg);
return newCfg;
},
resolvePathInfo: function (absId, cfg) {
// searches through the configured path mappings and packages
var pathMap, pathInfo, path, pkgCfg;
pathMap = cfg.pathMap;
if (!isAbsUrl(absId)) {
path = absId.replace(cfg.pathRx, function (match) {
// TODO: remove fallbacks here since they should never need to happen
pathInfo = pathMap[match] || {};
pkgCfg = pathInfo.config;
return pathInfo.path || '';
});
}
else {
path = absId;
}
return {
config: pkgCfg || userCfg,
url: core.resolveUrl(path, cfg)
};
},
resolveUrl: function (path, cfg) {
var baseUrl = cfg.baseUrl;
return baseUrl && !isAbsUrl(path) ? joinPath(baseUrl, path) : path;
},
checkToAddJsExt: function (url, cfg) {
// don't add extension if a ? is found in the url (query params)
// i'd like to move this feature to a moduleLoader
return url + ((cfg || userCfg).dontAddFileExt.test(url) ? '' : '.js');
},
loadScript: function (def, success, failure) {
// script processing rules learned from RequireJS
// TODO: pass a validate function into loadScript to check if a success really is a success
// insert script
var el = doc.createElement('script');
// initial script processing
function process (ev) {
ev = ev || global.event;
// detect when it's done loading
// ev.type == 'load' is for all browsers except IE6-9
// IE6-9 need to use onreadystatechange and look for
// el.readyState in {loaded, complete} (yes, we need both)
if (ev.type == 'load' || readyStates[el.readyState]) {
delete activeScripts[def.id];
// release event listeners
el.onload = el.onreadystatechange = el.onerror = ''; // ie cries if we use undefined
success();
}
}
function fail (e) {
// some browsers send an event, others send a string,
// but none of them send anything useful, so just say we failed:
failure(new Error('Syntax or http error: ' + def.url));
}
// set type first since setting other properties could
// prevent us from setting this later
// actually, we don't even need to set this at all
//el.type = 'text/javascript';
// using dom0 event handlers instead of wordy w3c/ms
el.onload = el.onreadystatechange = process;
el.onerror = fail;
// js! plugin uses alternate mimetypes
el.type = def.mimetype || 'text/javascript';
// TODO: support other charsets?
el.charset = 'utf-8';
el.async = !def.order;
el.src = def.url;
// loading will start when the script is inserted into the dom.
// IE will load the script sync if it's in the cache, so
// indicate the current resource definition if this happens.
activeScripts[def.id] = el;
head.insertBefore(el, insertBeforeEl);
// the js! plugin uses this
return el;
},
extractCjsDeps: function (defFunc) {
// Note: ignores require() inside strings and comments
var source, ids = [], currQuote;
// prefer toSource (FF) since it strips comments
source = typeof defFunc == 'string' ?
defFunc :
defFunc.toSource ? defFunc.toSource() : defFunc.toString();
// remove comments, then look for require() or quotes
source.replace(removeCommentsRx, '').replace(findRValueRequiresRx, function (m, rq, id, qq) {
// if we encounter a string in the source, don't look for require()
if (qq) {
currQuote = currQuote == qq ? undef : currQuote;
}
// if we're not inside a quoted string
else if (!currQuote) {
ids.push(id);
}
return ''; // uses least RAM/CPU
});
return ids;
},
fixArgs: function (args) {
// resolve args
// valid combinations for define:
// (string, array, object|function) sax|saf
// (array, object|function) ax|af
// (string, object|function) sx|sf
// (object|function) x|f
var id, deps, defFunc, defFuncArity, len, cjs;
len = args.length;
defFunc = args[len - 1];
defFuncArity = isType(defFunc, 'Function') ? defFunc.length : -1;
if (len == 2) {
if (isType(args[0], 'Array')) {
deps = args[0];
}
else {
id = args[0];
}
}
else if (len == 3) {
id = args[0];
deps = args[1];
}
// Hybrid format: assume that a definition function with zero
// dependencies and non-zero arity is a wrapped CommonJS module
if (!deps && defFuncArity > 0) {
cjs = true;
deps = ['require', 'exports', 'module'].slice(0, defFuncArity).concat(core.extractCjsDeps(defFunc));
}
return {
id: id,
deps: deps || [],
res: defFuncArity >= 0 ? defFunc : function () { return defFunc; },
cjs: cjs
};
},
executeDefFunc: function (def) {
var resource, moduleThis;
// the force of AMD is strong so anything returned
// overrides exports.
// node.js assumes `this` === `exports` so we do that
// for all cjs-wrapped modules, just in case.
// also, use module.exports if that was set
// (node.js convention).
// note: if .module exists, .exports exists.
moduleThis = def.cjs ? def.exports : undef;
resource = def.res.apply(moduleThis, def.deps);
if (resource === undef && def.exports) {
// note: exports will equal module.exports unless
// module.exports was reassigned inside module.
resource = def.module
? (def.exports = def.module['exports'])
: def.exports;
}
return resource;
},
defineResource: function (def, args) {
def.res = args.res;
def.cjs = args.cjs;
def.depNames = args.deps;
core.getDeps(def);
},
getDeps: function (parentDef) {
var i, names, deps, len, dep, completed, name,
exportCollector, resolveCollector;
deps = [];
names = parentDef.depNames;
len = names.length;
if (names.length == 0) allResolved();
function collect (dep, index, alsoExport) {
deps[index] = dep;
if (alsoExport) exportCollector(dep, index);
}
// reducer-collectors
exportCollector = countdown(len, collect, allExportsReady);
resolveCollector = countdown(len, collect, allResolved);
// initiate the resolution of all dependencies
// Note: the correct handling of early exports relies on the
// fact that the exports pseudo-dependency is always listed
// before other module dependencies.
for (i = 0; i < len; i++) {
name = names[i];
// is this "require", "exports", or "module"?
if (name in cjsGetters) {
// a side-effect of cjsGetters is that the cjs
// property is also set on the def.
resolveCollector(cjsGetters[name](parentDef), i, true);
// if we are using the `module` or `exports` cjs variables,
// signal any waiters/parents that we can export
// early (see progress callback in getDep below).
// note: this may fire for `require` as well, if it
// is listed after `module` or `exports` in the deps list,
// but that is okay since all waiters will only record
// it once.
if (parentDef.exports) {
parentDef.progress(msgUsingExports);
}
}
// check for blanks. fixes #32.
// this helps support yepnope.js, has.js, and the has! plugin
else if (!name) {
resolveCollector(undef, i, true);
}
// normal module or plugin resource
else {
getDep(name, i);
}
}
return parentDef;
function getDep (name, index) {
var resolveOnce, exportOnce, childDef, earlyExport;
resolveOnce = countdown(1, function (dep) {
exportOnce(dep);
resolveCollector(dep, index);
});
exportOnce = countdown(1, function (dep) {
exportCollector(dep, index);
});
// get child def / dep
childDef = core.fetchDep(name, parentDef);
// check if childDef can export. if it can, then
// we missed the notification and it will never fire in the
// when() below.
earlyExport = isPromise(childDef) && childDef.exports;
if (earlyExport) {
exportOnce(earlyExport);
}
when(childDef,
resolveOnce,
parentDef.reject,
parentDef.exports && function (msg) {
// messages are only sent from childDefs that support
// exports, and we only notify parents that understand
// exports too.
if (childDef.exports) {
if (msg == msgUsingExports) {
// if we're using exports cjs variable on both sides
exportOnce(childDef.exports);
}
else if (msg == msgFactoryExecuted) {
resolveOnce(childDef.exports);
}
}
}
);
}
function allResolved () {
parentDef.resolve(deps);
}
function allExportsReady () {
parentDef.exportsReady && parentDef.exportsReady(deps);
}
},
fetchResDef: function (def) {
// ensure url is computed
core.getDefUrl(def);
core.loadScript(def,
function () {
var args = argsNet;
argsNet = undef; // reset it before we get deps
// if our resource was not explicitly defined with an id (anonymous)
// Note: if it did have an id, it will be resolved in the define()
if (def.useNet !== false) {
// if !args, nothing was added to the argsNet
if (!args || args.ex) {
def.reject(new Error(((args && args.ex) || 'define() missing or duplicated: ' + def.url)));
}
else {
core.defineResource(def, args);
}
}
},
def.reject
);
return def;
},
fetchDep: function (depName, parentDef) {
var toAbsId, isPreload, parentCfg, parts, absId, mainId, loaderId, pluginId,
resId, pathInfo, def, tempDef, resCfg;
toAbsId = parentDef.toAbsId;
isPreload = parentDef.isPreload;
parentCfg = parentDef.config || userCfg; // is this fallback necessary?
absId = toAbsId(depName);
if (absId in cache) {
// module already exists in cache
mainId = absId;
}
else {
// check for plugin loaderId
parts = pluginParts(absId);
resId = parts.resourceId;
// get id of first resource to load (which could be a plugin)
mainId = parts.pluginId || resId;
pathInfo = core.resolvePathInfo(mainId, parentCfg);
}
if (!(absId in cache)) {
resCfg = core.resolvePathInfo(resId, parentCfg).config;
if (parts.pluginId) {
loaderId = mainId;
}
else {
// get custom module loader from package config if not a plugin
// TODO: move config.moduleLoader to config.loader
loaderId = resCfg['moduleLoader'] || resCfg.moduleLoader
|| resCfg['loader'] || resCfg.loader;
if (loaderId) {
// TODO: allow transforms to have relative module ids?
// (we could do this by returning package location from
// resolvePathInfo. why not return all package info?)
resId = mainId;
mainId = loaderId;
pathInfo = core.resolvePathInfo(loaderId, parentCfg);
}
}
}
if (mainId in cache) {
def = cache[mainId];
}
else if (pathInfo.url in urlCache) {
def = cache[mainId] = urlCache[pathInfo.url];
}
else {
def = core.createResourceDef(resCfg, mainId, isPreload);
// TODO: can this go inside createResourceDef?
// TODO: can we pass pathInfo.url to createResourceDef instead?
def.url = core.checkToAddJsExt(pathInfo.url, pathInfo.config);
cache[mainId] = urlCache[pathInfo.url] = def;
core.fetchResDef(def);
}
// plugin or transformer
if (mainId == loaderId) {
// use plugin's config if specified
if (parts.pluginId && parentCfg.plugins[parts.pluginId]) {
resCfg = parentCfg.plugins[parts.pluginId];
}
// we need to use an anonymous promise until plugin tells
// us normalized id. then, we need to consolidate the promises
// below. Note: exports objects will be different between
// pre-normalized and post-normalized defs! does this matter?
// don't put this resource def in the cache because if the
// resId doesn't change, the check if this is a new
// normalizedDef (below) will think it's already being loaded.
tempDef = new Promise();
// wait for plugin resource def
when(def, function(plugin) {
var normalizedDef, fullId, dynamic;
dynamic = plugin['dynamic'];
// check if plugin supports the normalize method
if ('normalize' in plugin) {
// note: dojo/has may return falsey values (0, actually)
resId = plugin['normalize'](resId, toAbsId, def.config) || '';
}
else {
resId = toAbsId(resId);
}
// use the full id (loaderId + id) to id plugin resources
// so multiple plugins may each process the same resource
// resId could be blank if the plugin doesn't require any (e.g. "domReady!")
fullId = loaderId + '!' + resId;
normalizedDef = cache[fullId];
// if this is our first time fetching this (normalized) def
if (!(fullId in cache)) {
// because we're using resId, plugins, such as wire!,
// can use paths relative to the resource
normalizedDef = core.createPluginDef(resCfg, fullId, resId, isPreload);
// don't cache non-determinate "dynamic" resources
if (!dynamic) {
cache[fullId] = normalizedDef;
}
// curl's plugins prefer to receive a deferred,
// but to be compatible with AMD spec, we have to
// piggy-back on the callback function parameter:
var loaded = function (res) {
if (!dynamic) cache[fullId] = res;
normalizedDef.resolve(res);
};
loaded['resolve'] = loaded;
loaded['reject'] = loaded['error'] = normalizedDef.reject;
// load the resource!
plugin.load(resId, normalizedDef.require, loaded, resCfg);
}
// chain defs (resolve when plugin.load executes)
if (tempDef != normalizedDef) {
when(normalizedDef, tempDef.resolve, tempDef.reject, tempDef.progress);
}
}, tempDef.reject);
}
// return tempDef if this is a plugin-based resource
return tempDef || def;
},
getCurrentDefName: function () {
// IE6-9 mark the currently executing thread as "interactive"
// Note: Opera lies about which scripts are "interactive", so we
// just have to test for it. Opera provides a true browser test, not
// a UA sniff, thankfully.
// learned this trick from James Burke's RequireJS
var def;
if (!isType(global.opera, 'Opera')) {
for (var d in activeScripts) {
if (activeScripts[d].readyState == 'interactive') {
def = d;
break;
}
}
}
return def;
},
findScript: function (predicate) {
var i = 0, scripts, script;
scripts = doc && (doc.scripts || doc.getElementsByTagName('script'));
while (scripts && (script = scripts[i++])) {
if (predicate(script)) return script;
}
},
extractDataAttrConfig: function () {
var script, attr = '';
script = core.findScript(function (script) {
var run;
// find data-curl-run attr on script element
run = script.getAttribute(bootScriptAttr);
if (run) attr = run;
return run;
});
// removeAttribute is wonky (in IE6?) but this works
if (script) {
script.setAttribute(bootScriptAttr, '');
}
return attr;
},
bootScript: function () {
var urls = bootScript.split(splitCommaSepRx);
if (urls.length) {
load();
}
function load () {
// Note: IE calls success handler if it gets a 400+.
core.loadScript({ url: urls.shift() }, check, check);
}
function check () {
// check if run.js called curl() or curl.config()
if (bootScript) {
if (urls.length) {
// show an error message
core.nextTurn(fail);
// try next
load();
}
else fail('run.js script did not run.');
}
}
function fail (msg) {
throw new Error(msg || 'Primary run.js failed. Trying fallback.');
}
},
nextTurn: function (task) {
setTimeout(task, 0);
}
};
// hook-up cjs free variable getters
cjsGetters = {'require': core.getCjsRequire, 'exports': core.getCjsExports, 'module': core.getCjsModule};
function _curl (/* various */) {
var args, promise, cfg;
// indicate we're no longer waiting for a boot script
bootScript = '';
args = [].slice.call(arguments);
// extract config, if it's specified
if (isType(args[0], 'Object')) {
cfg = args.shift();
promise = _config(cfg);
}
return new CurlApi(args[0], args[1], args[2], promise);
}
function _config (cfg, callback, errback) {
var pPromise, main, fallback;
// indicate we're no longer waiting for a boot script
bootScript = '';
if (cfg) {
core.setApi(cfg);
userCfg = core.config(cfg);
// check for preloads
if ('preloads' in cfg) {
pPromise = new CurlApi(cfg['preloads'], undef, errback, preload, true);
// yes, this is hacky and embarrassing. now that we've got that
// settled... until curl has deferred factory execution, this
// is the only way to stop preloads from dead-locking when
// they have dependencies inside a bundle.
core.nextTurn(function () { preload = pPromise; });
}
// check for main module(s). all modules wait for preloads implicitly.
main = cfg['main'];
if (main) {
return new CurlApi(main, callback, errback);
}
}
}
// thanks to Joop Ringelberg for helping troubleshoot the API
function CurlApi (ids, callback, errback, waitFor, isPreload) {
var then, ctx;
ctx = core.createContext(userCfg, undef, [].concat(ids), isPreload);
this['then'] = this.then = then = function (resolved, rejected) {
when(ctx,
// return the dependencies as arguments, not an array
function (deps) {
if (resolved) resolved.apply(undef, deps);
},
// just throw if the dev didn't specify an error handler
function (ex) {
if (rejected) rejected(ex); else throw ex;
}
);
return this;
};
this['next'] = function (ids, cb, eb) {
// chain api
return new CurlApi(ids, cb, eb, ctx);
};
this['config'] = _config;
if (callback || errback) then(callback, errback);
// ensure next-turn so inline code can execute first
core.nextTurn(function () {
when(isPreload || preload, function () {
when(waitFor, function () { core.getDeps(ctx); }, errback);
});
});
}
_curl['version'] = version;
_curl['config'] = _config;
function _define (args) {
var id, def, pathInfo;
id = args.id;
if (id == undef) {
if (argsNet !== undef) {
argsNet = { ex: 'Multiple anonymous defines encountered' };
}
else if (!(id = core.getCurrentDefName())/* intentional assignment */) {
// anonymous define(), defer processing until after script loads
argsNet = args;
}
}
if (id != undef) {
// named define(), it is in the cache if we are loading a dependency
// (could also be a secondary define() appearing in a built file, etc.)
def = cache[id];
if (!(id in cache)) {
// id is an absolute id in this case, so we can get the config.
pathInfo = core.resolvePathInfo(id, userCfg);
def = core.createResourceDef(pathInfo.config, id);
cache[id] = def;
}
if (!isPromise(def)) throw new Error('duplicate define: ' + id);
// check if this resource has already been resolved
def.useNet = false;
core.defineResource(def, args);
}
}
function define () {
// wrap inner _define so it can be replaced without losing define.amd
var args = core.fixArgs(arguments);
_define(args);
}
// indicate our capabilities:
define['amd'] = { 'plugins': true, 'jQuery': true, 'curl': version };
// default configs
userCfg = {
baseUrl: '',
pluginPath: 'curl/plugin',
dontAddFileExt: dontAddExtRx,
paths: {},
packages: {},
plugins: {},
pathMap: {},
pathRx: /$^/
};
// handle pre-existing global
prevCurl = global[curlName];
prevDefine = global[defineName];
// only run config if there is something to config (perf saver?)
if (prevCurl && isType(prevCurl, 'Object')) {
// remove global curl object
global[curlName] = undef; // can't use delete in IE 6-8
// configure curl
_config(prevCurl);
}
else {
// set default api
core.setApi();
}
// look for "data-curl-run" directive
bootScript = core.extractDataAttrConfig();
// wait a bit in case curl.js is bundled into the boot script
if (bootScript) core.nextTurn(core.bootScript);
// allow curl to be a dependency
cache[curlName] = _curl;
// expose curl core for special plugins and modules
// Note: core overrides will only work in either of two scenarios:
// 1. the files are running un-compressed (Google Closure or Uglify)
// 2. the overriding module was compressed into the same file as curl.js
// Compiling curl and the overriding module separately won't work.
cache['curl/_privileged'] = {
'core': core,
'cache': cache,
'config': function () { return userCfg; },
'_define': _define,
'_curl': _curl,
'Promise': Promise
};
}(this.window || (typeof global != 'undefined' && global) || this));