forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreload.js
227 lines (201 loc) · 7.39 KB
/
reload.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
222
223
224
225
226
227
/**
* This code does _NOT_ support hot (session-restoring) reloads on
* IE6,7. It only works on browsers with sessionStorage support.
*
* There are a couple approaches to add IE6,7 support:
*
* - use IE's "userData" mechanism in combination with window.name.
* This mostly works, however the problem is that it can not get to the
* data until after DOMReady. This is a problem for us since this API
* relies on the data being ready before API users run. We could
* refactor using Meteor.startup in all API users, but that might slow
* page loads as we couldn't start the stream until after DOMReady.
* Here are some resources on this approach:
* https://github.com/hugeinc/USTORE.js
* http://thudjs.tumblr.com/post/419577524/localstorage-userdata
* http://www.javascriptkit.com/javatutors/domstorage2.shtml
*
* - POST the data to the server, and have the server send it back on
* page load. This is nice because it sidesteps all the local storage
* compatibility issues, however it is kinda tricky. We can use a unique
* token in the URL, then get rid of it with HTML5 pushstate, but that
* only works on pushstate browsers.
*
* This will all need to be reworked entirely when we add server-side
* HTML rendering. In that case, the server will need to have access to
* the client's session to render properly.
*/
// XXX when making this API public, also expose a flag for the app
// developer to know whether a hot code push is happening. This is
// useful for apps using `window.onbeforeunload`. See
// https://github.com/meteor/meteor/pull/657
Reload = {};
var KEY_NAME = 'Meteor_Reload';
var old_data = {};
// read in old data at startup.
var old_json;
// This logic for sessionStorage detection is based on browserstate/history.js
var safeSessionStorage = null;
try {
// This throws a SecurityError on Chrome if cookies & localStorage are
// explicitly disabled
//
// On Firefox with dom.storage.enabled set to false, sessionStorage is null
//
// We can't even do (typeof sessionStorage) on Chrome, it throws. So we rely
// on the throw if sessionStorage == null; the alternative is browser
// detection, but this seems better.
safeSessionStorage = window.sessionStorage;
// Check we can actually use it
if (safeSessionStorage) {
safeSessionStorage.setItem('__dummy__', '1');
safeSessionStorage.removeItem('__dummy__');
} else {
// Be consistently null, for safety
safeSessionStorage = null;
}
} catch(e) {
// Expected on chrome with strict security, or if sessionStorage not supported
safeSessionStorage = null;
}
// Exported for test.
Reload._getData = function () {
return safeSessionStorage && safeSessionStorage.getItem(KEY_NAME);
};
if (safeSessionStorage) {
old_json = Reload._getData();
safeSessionStorage.removeItem(KEY_NAME);
} else {
// Unsupported browser (IE 6,7) or locked down security settings.
// No session resumption.
// Meteor._debug("XXX UNSUPPORTED BROWSER/SETTINGS");
}
if (!old_json) old_json = '{}';
var old_parsed = {};
try {
old_parsed = JSON.parse(old_json);
if (typeof old_parsed !== "object") {
Meteor._debug("Got bad data on reload. Ignoring.");
old_parsed = {};
}
} catch (err) {
Meteor._debug("Got invalid JSON on reload. Ignoring.");
}
if (old_parsed.reload && typeof old_parsed.data === "object") {
// Meteor._debug("Restoring reload data.");
old_data = old_parsed.data;
}
var providers = [];
////////// External API //////////
// Packages that support migration should register themselves by calling
// this function. When it's time to migrate, callback will be called
// with one argument, the "retry function," and an optional 'option'
// argument (containing a key 'immediateMigration'). If the package
// is ready to migrate, it should return [true, data], where data is
// its migration data, an arbitrary JSON value (or [true] if it has
// no migration data this time). If the package needs more time
// before it is ready to migrate, it should return false. Then, once
// it is ready to migrating again, it should call the retry
// function. The retry function will return immediately, but will
// schedule the migration to be retried, meaning that every package
// will be polled once again for its migration data. If they are all
// ready this time, then the migration will happen. name must be set if there
// is migration data. If 'immediateMigration' is set in the options
// argument, then it doesn't matter whether the package is ready to
// migrate or not; the reload will happen immediately without waiting
// (used for OAuth redirect login).
//
Reload._onMigrate = function (name, callback) {
if (!callback) {
// name not provided, so first arg is callback.
callback = name;
name = undefined;
}
providers.push({name: name, callback: callback});
};
// Called by packages when they start up.
// Returns the object that was saved, or undefined if none saved.
//
Reload._migrationData = function (name) {
return old_data[name];
};
// Options are the same as for `Reload._migrate`.
var pollProviders = function (tryReload, options) {
tryReload = tryReload || function () {};
options = options || {};
var migrationData = {};
var remaining = _.clone(providers);
var allReady = true;
while (remaining.length) {
var p = remaining.shift();
var status = p.callback(tryReload, options);
if (!status[0])
allReady = false;
if (status.length > 1 && p.name)
migrationData[p.name] = status[1];
};
if (allReady || options.immediateMigration)
return migrationData;
else
return null;
};
// Options are:
// - immediateMigration: true if the page will be reloaded immediately
// regardless of whether packages report that they are ready or not.
Reload._migrate = function (tryReload, options) {
// Make sure each package is ready to go, and collect their
// migration data
var migrationData = pollProviders(tryReload, options);
if (migrationData === null)
return false; // not ready yet..
try {
// Persist the migration data
var json = JSON.stringify({
data: migrationData, reload: true
});
} catch (err) {
Meteor._debug("Couldn't serialize data for migration", migrationData);
throw err;
}
if (safeSessionStorage) {
try {
safeSessionStorage.setItem(KEY_NAME, json);
} catch (err) {
// We should have already checked this, but just log - don't throw
Meteor._debug("Couldn't save data for migration to sessionStorage", err);
}
} else {
Meteor._debug("Browser does not support sessionStorage. Not saving migration state.");
}
return true;
};
// Allows tests to isolate the list of providers.
Reload._withFreshProvidersForTest = function (f) {
var originalProviders = _.clone(providers);
providers = [];
try {
f();
} finally {
providers = originalProviders;
}
};
// Migrating reload: reload this page (presumably to pick up a new
// version of the code or assets), but save the program state and
// migrate it over. This function returns immediately. The reload
// will happen at some point in the future once all of the packages
// are ready to migrate.
//
var reloading = false;
Reload._reload = function (options) {
options = options || {};
if (reloading)
return;
reloading = true;
var tryReload = function () { _.defer(function () {
if (Reload._migrate(tryReload, options)) {
// Tell the browser to shut down this VM and make a new one
window.location.reload();
}
}); };
tryReload();
};