forked from olark/lightningjs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lightningjs-bootstrap.js
221 lines (202 loc) · 8.85 KB
/
lightningjs-bootstrap.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
window.lightningjs || (function(window, parentLightningjs){
// get a handle on the parent copy of lightningjs
var innerLightningjs = window.lightningjs = {modules: parentLightningjs.modules},
modules = parentLightningjs.modules;
// export the rest of the lightningjs APIs
innerLightningjs.expensive = function(callback) {
callback._waitforload = true;
return callback;
}
innerLightningjs.require = parentLightningjs.require;
innerLightningjs.provide = function(ns, api) {
// in case we are calling provide() without having ever require()d it,
// make sure that we create the default callstack, etc
innerLightningjs.require(ns);
// determine if we are providing this module twice
function logError() {
var c = window.console;
if (c && c.error) {
try {
c.error.apply(c, arguments);
} catch(e) {}
} else if (window.opera) {
try {
window.opera.postError.apply(window.opera, arguments);
} catch(e) {}
}
}
var root = modules[ns];
if (root.provided) {
// already defined this module
logError("deferred module '" + ns + "' is already defined");
return;
} else {
// we are about to define this module
root.provided = true;
}
// start calling all pending deferreds and future deferreds in order
var deferredApiCalls = (root._.s||[]).slice(),
responses = {0: api},
parentLoadPendingCalls = [],
parentLoadPendingIdLookup = {},
parentLoaded = false;
// this is a workaround for potential issue when window.id was set
// in older versions of the embed code
if(deferredApiCalls && deferredApiCalls[0]) {
var promiseFunctionId = deferredApiCalls[0][1];
responses[promiseFunctionId] = api;
}
// NOTE: root.lv contains the embed version
api._load = function() {
// this method gets called whenever the parent.window.onload event fires
parentLoaded = true;
// dequeue all pending load calls
var nextCall = parentLoadPendingCalls.shift();
while (nextCall) {
executeCall(nextCall);
nextCall = parentLoadPendingCalls.shift();
}
}
function enqueueCallThatDependsOnParentLoad(call) {
var methodResponseId = call[0];
parentLoadPendingIdLookup[methodResponseId] = true;
parentLoadPendingCalls.push(call);
}
function callDependsOnParentLoad(call) {
if (parentLoaded) {
return false;
}
var methodResponseId = call[0],
methodSourceId = call[1],
methodSource = methodSourceId > 0 ? responses[methodSourceId] : api,
methodArguments = Array.prototype.slice.call(call[2]),
methodName = methodArguments.shift(),
method,
isWaitingOnAnotherPendingCall = parentLoadPendingIdLookup[methodSourceId] ? true : false;
if (methodSource) {
method = methodSource[methodName];
if (method) {
return method._waitforload ? true : false;
} else {
// method doesn't exist, so it doesn't depend on parent load
return false;
}
} else if (isWaitingOnAnotherPendingCall) {
return true;
} else {
// method depends on a response that will never exist
return false;
}
}
function executeCall(call) {
var methodResponseId = call[0],
methodSourceId = call[1],
methodSource = methodSourceId > 0 ? responses[methodSourceId] : api,
methodArguments = Array.prototype.slice.call(call[2]),
methodName = methodArguments.shift(),
methodFulfillmentHandlers = root._.fh[methodResponseId] = root._.fh[methodResponseId] || [],
methodErrorHandlers = root._.eh[methodResponseId] = root._.eh[methodResponseId] || [],
// TODO: progress handling is not implemented yet
methodProgressHandlers = root._.ph[methodResponseId] = root._.ph[methodResponseId] || [],
method,
methodResponse,
methodError;
// reconstruct the call and perform it on the API
if (methodSource) {
// this is a deferred call on the root API namespace
method = methodSource[methodName];
if (method) {
// call the deferred method
try {
methodResponse = method.apply(method, methodArguments);
} catch(e) {
methodError = e;
}
} else {
// no methods matched for this call
// TODO: consider some kind of method_missing approach here?
methodError = new Error("unknown deferred method '" + methodName + "'");
logError(methodError.toString());
}
// cache the response so that dependent calls can reference it
// later on in the callstack
if (methodResponse) {
responses[methodResponseId] = methodResponse;
}
// ensure that the proper callbacks and errorHandlers get called
if (methodError) {
while (methodErrorHandlers.length) {
var errorHandler = methodErrorHandlers.shift();
try {
errorHandler(methodError);
} catch(e) {
logError(e);
}
}
// ensure all future callbacks get called too
methodErrorHandlers.push = function(errorHandler) {
errorHandler(methodError);
}
} else {
while (methodFulfillmentHandlers.length) {
var fulfillmentHandler = methodFulfillmentHandlers.shift();
try {
fulfillmentHandler(methodResponse);
} catch(e) {
logError(e);
}
}
// ensure all future callbacks get called too
methodFulfillmentHandlers.push = function(fulfillmentHandler) {
fulfillmentHandler(methodResponse);
}
}
} else {
logError("cannot call deferred method '" + methodName + "' on 'undefined'")
}
}
function dequeueDeferredApiCalls() {
var nextCall = deferredApiCalls.shift();
while (nextCall) {
if (callDependsOnParentLoad(nextCall)) {
// we have to wait for parent to load here
enqueueCallThatDependsOnParentLoad(nextCall);
} else {
executeCall(nextCall);
}
// dequeue the next call
nextCall = deferredApiCalls.shift();
}
}
// root._.s is the callstack from the embed code, the format is a list of
// tuples like this: [responseId, sourceId, argumentList]
// ...we change it's push() method here to start triggering dequeuing
// of those calls since we have the library provided now
root._.s = {push: function(deferredArguments){
deferredApiCalls.push(deferredArguments);
dequeueDeferredApiCalls();
}};
// start dequeing calls immediately
dequeueDeferredApiCalls();
}
// provide a lightningjs('load') method that signals all other modules to load
if (modules.lightningjs.provided) {
// lightningjs deferred methods are already defined
} else {
// define lightningjs deferred methods
innerLightningjs.provide('lightningjs', {
// helper that allows forced load (could be used to reload modules)
// TODO: is this necessary anymore?
'load': function() {
var modules = parentLightningjs.modules,
moduleObj;
for (var moduleName in modules) {
moduleObj = modules[moduleName];
if (moduleObj._) {
moduleObj('_load')
}
}
}
})
}
})(window, window.parent.lightningjs);