unpoly-rails 0.56.3 → 0.56.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46bc72e64b08abcd546058f0c7de5eb6689b838c8381238f3a836c2c14ebbb08
4
- data.tar.gz: 58e000985b0e293c50fc1c45b9c2cb86d84f9eff296c1d0965d3521f4534570c
3
+ metadata.gz: bbf541329562b0646bf5920f6d1f80292ad728bc2638bfb42ab1b5eab462d043
4
+ data.tar.gz: b4b541fd4b9e5dc1a0529b8d181895e77c3fced3b86fa1ff0570399a9926936c
5
5
  SHA512:
6
- metadata.gz: 50c89ee2df8e468ab5d83fa45a1329b3f9c33b31191b6387265384d730ed93ddfeaf3bf63a72acf9c77ac3bbed14d8223467b2d49692760acc44f1fd45935ede
7
- data.tar.gz: bd35ed5207ec16d7b0c201ec454f92a2b0074307adee2aca22d6819644bba3bf28d1da746cbf926a3d60be40af84dc16bd11641670cd7751b84f23049ec93de5
6
+ metadata.gz: 3e5608333fb47140165614963d5c98b675997c778999659e85fd7dab67b6924abbbe987a4978d9c13aa74437b9f602769cb9d0089a639877ee12379c84f4f8fd
7
+ data.tar.gz: 6d52bd257bc4296f30a784e6c688dda9e6725dc0928bed76bcc0898784a555fb36eba52167ac6b2f894b46056f299b7851b84d535149ef0ecaf455902e665861
data/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ Changes to this project will be documented in this file.
6
6
  This project mostly adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
8
 
9
+ 0.56.4
10
+ ------
11
+
12
+ - Improve performance of HTML parsing.
13
+
14
+
9
15
  0.56.3
10
16
  ------
11
17
 
@@ -0,0 +1,319 @@
1
+ ITERATIONS = 100;
2
+ CHUNK_SIZE = 10;
3
+
4
+ HTML = "<body>" + document.body.innerHTML + "</body>";
5
+
6
+ QUEUE = [];
7
+
8
+ function pokeQueue() {
9
+ var job;
10
+ if (job = QUEUE.shift()) {
11
+ job();
12
+ setTimeout(pokeQueue, 15);
13
+ }
14
+ }
15
+
16
+ function queue(fn) {
17
+ QUEUE.push(fn);
18
+ }
19
+
20
+
21
+ ESCAPE_HTML_ENTITY_MAP = {
22
+ "&": "&amp;",
23
+ "<": "&lt;",
24
+ ">": "&gt;",
25
+ '"': '&quot;'
26
+ };
27
+
28
+ /***
29
+ Escapes the given string of HTML by replacing control chars with their HTML entities.
30
+
31
+ @function up.util.escapeHtml
32
+ @param {string} string
33
+ The text that should be escaped
34
+ @experimental
35
+ */
36
+ escapeHtml = function(string) {
37
+ return string.replace(/[&<>"]/g, function(char) {
38
+ return ESCAPE_HTML_ENTITY_MAP[char];
39
+ });
40
+ };
41
+
42
+
43
+
44
+ function experiment(name, fn) {
45
+ performance.clearMarks();
46
+ performance.clearMeasures();
47
+
48
+ queue(function() {
49
+ // warm-up
50
+ for (var i = 0; i < 100; i++) {
51
+ fn();
52
+ }
53
+ })
54
+
55
+ var chunkCount = Math.floor(ITERATIONS / CHUNK_SIZE);
56
+
57
+ for (var i = 0; i < chunkCount; i++) {
58
+ queue(function() {
59
+ performance.mark(name + "-start");
60
+ for (var j = 0; j < CHUNK_SIZE; j++) {
61
+ fn();
62
+ }
63
+ performance.mark(name + "-end");
64
+ performance.measure(name, name + "-start", name + "-end");
65
+ })
66
+ }
67
+
68
+ queue(function() {
69
+ var entries = performance.getEntriesByName(name);
70
+ var durations = entries.map(function (e) {
71
+ return e.duration
72
+ });
73
+
74
+ var outlyerLength = Math.floor(chunkCount * 0.01);
75
+
76
+ durations = durations.slice(outlyerLength, durations.length - outlyerLength);
77
+
78
+ durations.sort(function (a, b,) {
79
+ return a - b
80
+ });
81
+
82
+ var times = {
83
+ minimum: durations[0],
84
+ maximum: durations[durations.length - 1],
85
+ median: durations[Math.floor(durations.length / 2)],
86
+ total: durations.reduce(function (total, num) {
87
+ return total + num
88
+ }),
89
+ setSize: durations.length
90
+ }
91
+
92
+ console.debug("Experiment %s: %o", name, times);
93
+ })
94
+ }
95
+
96
+
97
+ fixScripts = function(element) {
98
+ var fix, fixes, i, len, results;
99
+ fixes = [];
100
+ detectScriptFixes(element, fixes);
101
+ for (i = 0, len = fixes.length; i < len; i++) {
102
+ fix = fixes[i];
103
+ fix();
104
+ }
105
+ };
106
+
107
+ detectScriptFixes = function(element, fixes) {
108
+ var child, clone, i, len, ref, results;
109
+ if (element.tagName === 'NOSCRIPT') {
110
+ clone = document.createElement('noscript');
111
+ clone.textContent = element.innerHTML;
112
+ return fixes.push(function() {
113
+ return element.parentNode.replaceChild(clone, element);
114
+ });
115
+ } else if (element.tagName === 'SCRIPT') {
116
+ return fixes.push(function() {
117
+ return element.parentNode.removeChild(element);
118
+ });
119
+ } else {
120
+ ref = element.children;
121
+ results = [];
122
+ for (i = 0, len = ref.length; i < len; i++) {
123
+ child = ref[i];
124
+ results.push(detectScriptFixes(child, fixes));
125
+ }
126
+ return results;
127
+ }
128
+ };
129
+
130
+ function removeScripts(doc) {
131
+ doc.querySelectorAll('script').forEach(function(e) { e.remove() })
132
+ }
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+ domParser = new DOMParser();
141
+
142
+
143
+ /*
144
+ experiment('PlainDOMParser', function() {
145
+ var doc = domParser.parseFromString(HTML, "text/html");
146
+ var body = doc.querySelector('body');
147
+ if (!body) {
148
+ throw "parsing failed";
149
+ }
150
+ });
151
+ */
152
+
153
+ experiment('DOMParserWithFixScripts', function() {
154
+ var doc = domParser.parseFromString(HTML, "text/html");
155
+ var body = doc.querySelector('body');
156
+ if (!body) {
157
+ throw "parsing failed";
158
+ }
159
+ fixScripts(body);
160
+ });
161
+
162
+
163
+ /*
164
+ experiment('DOMParserWithImport', function() {
165
+ var doc = domParser.parseFromString(HTML, "text/html");
166
+ var body = doc.querySelector('body');
167
+ if (!body) {
168
+ throw "no body in imported";
169
+ }
170
+ var importedNode = document.importNode(body, true);
171
+ });
172
+ */
173
+
174
+
175
+ var noScriptPattern = /<noscript[^>]*>(.*?)<\/noscript>/i
176
+
177
+ experiment('DOMParserWitSmartFixScripts', function() {
178
+ var requiresNoscriptFix = true;
179
+
180
+ var html = HTML;
181
+
182
+ if (requiresNoscriptFix) {
183
+ html = html.replace(noScriptPattern, function (match, content) {
184
+ return '<div class="up-noscript" data-html="' + escapeHtml(content) + '"></div>'
185
+ })
186
+ }
187
+
188
+ var doc = domParser.parseFromString(html, "text/html");
189
+
190
+ var body = doc.querySelector('body');
191
+ if (!body) {
192
+ throw "no body in imported";
193
+ }
194
+ removeScripts(body);
195
+
196
+ if (requiresNoscriptFix) {
197
+ var noscripts = body.querySelectorAll('.up-noscript');
198
+ noscripts.forEach(function(noscript) {
199
+ var html = noscript.getAttribute('data-html');
200
+ noscript.outerHTML = html;
201
+ })
202
+ }
203
+ });
204
+
205
+
206
+
207
+
208
+
209
+
210
+ experiment('DOMParserWithImportAndFixScripts', function() {
211
+ var doc = domParser.parseFromString(HTML, "text/html");
212
+ var body = doc.querySelector('body');
213
+ if (!body) {
214
+ throw "no body in imported";
215
+ }
216
+ var importedNode = document.importNode(body, true);
217
+ fixScripts(importedNode);
218
+ });
219
+
220
+
221
+
222
+
223
+
224
+ var noScriptPattern = /<noscript[^>]*>(.*?)<\/noscript>/i
225
+ // var customElementPattern = /<[^!\->\s\/]+\-/;
226
+ var customElementPattern = /<\w+-\w+/;
227
+
228
+ experiment('DOMParserWithOnDemandImportAndFixScript', function() {
229
+ var requiresNoscriptFix = true;
230
+
231
+ var html = HTML;
232
+
233
+ if (requiresNoscriptFix) {
234
+ html = html.replace(noScriptPattern, function (match, content) {
235
+ return '<div class="up-noscript" data-html="' + escapeHtml(content) + '"></div>'
236
+ })
237
+ }
238
+
239
+ var doc = domParser.parseFromString(html, "text/html");
240
+
241
+ var body = doc.querySelector('body');
242
+ if (!body) {
243
+ throw "no body in imported";
244
+ }
245
+
246
+ var importedNode;
247
+
248
+ if (customElementPattern.test(html)) {
249
+ console.debug("importing because found custom element: ", customElementPattern.exec(html));
250
+ importedNode = document.importNode(body, true);
251
+ } else {
252
+ importedNode = body;
253
+ }
254
+
255
+ // removeScripts(importedNode);
256
+
257
+ if (requiresNoscriptFix) {
258
+ var noscripts = importedNode.querySelectorAll('.up-noscript');
259
+ noscripts.forEach(function(noscript) {
260
+ var html = noscript.getAttribute('data-html');
261
+ noscript.outerHTML = html;
262
+ })
263
+ }
264
+ });
265
+
266
+
267
+
268
+ experiment('DocumentFragmentInnerHTMLWithRemoveScripts', function() {
269
+
270
+ var doc = document.createDocumentFragment();
271
+ var htmlNode = document.createElement('html');
272
+ doc.appendChild(htmlNode);
273
+
274
+ htmlNode.innerHTML = HTML;
275
+
276
+ var body = doc.querySelector('body');
277
+ if (!body) {
278
+ throw "no body in imported";
279
+ }
280
+
281
+ removeScripts(body);
282
+ });
283
+
284
+
285
+ var htmlPattern = /<html[^>]*>(.*?)<\/html>/i;
286
+ var bodyPattern = /<body[^>]*>/i;
287
+
288
+
289
+ experiment('SmartInnerHTMLWithRemoveScripts', function() {
290
+
291
+ var doc = undefined;
292
+
293
+ if (match = htmlPattern.exec(HTML)) {
294
+ var htmlNode = document.createElement('html');
295
+ htmlNode.innerHTML = match[1];
296
+ doc = htmlNode;
297
+
298
+ } else if (bodyPattern.test(HTML)) {
299
+ var htmlNode = document.createElement('html');
300
+ htmlNode.innerHTML = HTML;
301
+ doc = htmlNode;
302
+ } else {
303
+ var divNode = document.createElement('div');
304
+ divNode.innerHTML = HTML;
305
+ doc = divNode;
306
+ }
307
+
308
+ var body = doc.querySelector('body');
309
+ if (!body) {
310
+ throw "no body in imported";
311
+ }
312
+
313
+ removeScripts(body);
314
+
315
+ });
316
+
317
+
318
+
319
+ pokeQueue();
data/dist/unpoly.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  (function() {
7
7
  window.up = {
8
- version: "0.56.3",
8
+ version: "0.56.4",
9
9
  renamedModule: function(oldName, newName) {
10
10
  return typeof Object.defineProperty === "function" ? Object.defineProperty(up, oldName, {
11
11
  get: function() {
@@ -3393,6 +3393,70 @@ that might save you from loading something like [Lodash](https://lodash.com/).
3393
3393
 
3394
3394
  })();
3395
3395
 
3396
+ }).call(this);
3397
+ (function() {
3398
+ var u;
3399
+
3400
+ u = up.util;
3401
+
3402
+ up.HtmlParser = (function() {
3403
+ function HtmlParser(html) {
3404
+ this.html = html;
3405
+ this.wrapNoscriptInHtml();
3406
+ this.parsedDoc = u.createElementFromHtml(this.html);
3407
+ }
3408
+
3409
+ HtmlParser.prototype.title = function() {
3410
+ var ref;
3411
+ return (ref = this.parsedDoc.querySelector("head title")) != null ? ref.textContent : void 0;
3412
+ };
3413
+
3414
+ HtmlParser.prototype.first = function(selector) {
3415
+ var match;
3416
+ if (match = $.find(selector, this.parsedDoc)[0]) {
3417
+ return $(match);
3418
+ }
3419
+ };
3420
+
3421
+ HtmlParser.prototype.prepareForInsertion = function($element) {
3422
+ var element;
3423
+ element = $element[0];
3424
+ this.unwrapNoscriptInElement(element);
3425
+ return $(element);
3426
+ };
3427
+
3428
+ HtmlParser.prototype.wrapNoscriptInHtml = function() {
3429
+ var noscriptPattern;
3430
+ noscriptPattern = /<noscript[^>]*>((.|\s)*?)<\/noscript>/i;
3431
+ return this.html = this.html.replace(noscriptPattern, (function(_this) {
3432
+ return function(match, content) {
3433
+ _this.didWrapNoscript = true;
3434
+ return '<div class="up-noscript" data-html="' + u.escapeHtml(content) + '"></div>';
3435
+ };
3436
+ })(this));
3437
+ };
3438
+
3439
+ HtmlParser.prototype.unwrapNoscriptInElement = function(element) {
3440
+ var i, len, noscript, results, wrappedContent, wrappedNoscript, wrappedNoscripts;
3441
+ if (!this.didWrapNoscript) {
3442
+ return;
3443
+ }
3444
+ wrappedNoscripts = element.querySelectorAll('.up-noscript');
3445
+ results = [];
3446
+ for (i = 0, len = wrappedNoscripts.length; i < len; i++) {
3447
+ wrappedNoscript = wrappedNoscripts[i];
3448
+ wrappedContent = wrappedNoscript.getAttribute('data-html');
3449
+ noscript = document.createElement('noscript');
3450
+ noscript.textContent = wrappedContent;
3451
+ results.push(wrappedNoscript.parentNode.replaceChild(noscript, wrappedNoscript));
3452
+ }
3453
+ return results;
3454
+ };
3455
+
3456
+ return HtmlParser;
3457
+
3458
+ })();
3459
+
3396
3460
  }).call(this);
3397
3461
  (function() {
3398
3462
  var u,
@@ -4167,7 +4231,7 @@ Internet Explorer 10 or lower
4167
4231
  var slice = [].slice;
4168
4232
 
4169
4233
  up.browser = (function($) {
4170
- var CONSOLE_PLACEHOLDERS, canConsole, canCssTransition, canDOMParser, canFormData, canInputEvent, canPromise, canPushState, hash, isIE10OrWorse, isRecentJQuery, isSupported, navigate, polyfilledSessionStorage, popCookie, puts, sessionStorage, sprintf, sprintfWithFormattedArgs, stringifyArg, submitForm, u, url, whenConfirmed;
4234
+ var CONSOLE_PLACEHOLDERS, canConsole, canCssTransition, canCustomElements, canDOMParser, canFormData, canInputEvent, canPromise, canPushState, hash, isIE10OrWorse, isRecentJQuery, isSupported, navigate, polyfilledSessionStorage, popCookie, puts, sessionStorage, sprintf, sprintfWithFormattedArgs, stringifyArg, submitForm, u, url, whenConfirmed;
4171
4235
  u = up.util;
4172
4236
 
4173
4237
  /***
@@ -4388,6 +4452,9 @@ Internet Explorer 10 or lower
4388
4452
  canConsole = u.memoize(function() {
4389
4453
  return window.console && console.debug && console.info && console.warn && console.error && console.group && console.groupCollapsed && console.groupEnd;
4390
4454
  });
4455
+ canCustomElements = u.memoize(function() {
4456
+ return !!window.customElements;
4457
+ });
4391
4458
  isRecentJQuery = u.memoize(function() {
4392
4459
  var major, minor, parts, version;
4393
4460
  version = $.fn.jquery;
@@ -4492,6 +4559,7 @@ Internet Explorer 10 or lower
4492
4559
  navigate: navigate,
4493
4560
  submitForm: submitForm,
4494
4561
  canPushState: canPushState,
4562
+ canCustomElements: canCustomElements,
4495
4563
  whenConfirmed: whenConfirmed,
4496
4564
  isSupported: isSupported,
4497
4565
  puts: puts,
@@ -7309,7 +7377,7 @@ is built from these functions. You can use them to extend Unpoly from your
7309
7377
 
7310
7378
  (function() {
7311
7379
  up.dom = (function($) {
7312
- var bestMatchingSteps, bestPreflightSelector, config, destroy, detectScriptFixes, emitFragmentDestroy, emitFragmentDestroyed, emitFragmentInserted, emitFragmentKept, extract, findKeepPlan, first, firstInLayer, firstInPriority, fixScripts, hello, isRealElement, layerOf, markElementAsDestroying, matchesLayer, parseResponseDoc, processResponse, reload, replace, reset, resolveSelector, setSource, shouldExtractTitle, shouldLogDestruction, source, swapElements, transferKeepableElements, u, updateHistoryAndTitle;
7380
+ var bestMatchingSteps, bestPreflightSelector, config, destroy, emitFragmentDestroy, emitFragmentDestroyed, emitFragmentInserted, emitFragmentKept, extract, findKeepPlan, first, firstInLayer, firstInPriority, hello, isRealElement, layerOf, markElementAsDestroying, matchesLayer, processResponse, reload, replace, reset, resolveSelector, setSource, shouldExtractTitle, shouldLogDestruction, source, swapElements, transferKeepableElements, u, updateHistoryAndTitle;
7313
7381
  u = up.util;
7314
7382
 
7315
7383
  /***
@@ -7715,7 +7783,7 @@ is built from these functions. You can use them to extend Unpoly from your
7715
7783
  if (typeof options.provideTarget === "function") {
7716
7784
  options.provideTarget();
7717
7785
  }
7718
- responseDoc = parseResponseDoc(html);
7786
+ responseDoc = new up.HtmlParser(html);
7719
7787
  extractSteps = bestMatchingSteps(selectorOrElement, responseDoc, options);
7720
7788
  if (shouldExtractTitle(options) && (responseTitle = responseDoc.title())) {
7721
7789
  options.title = responseTitle;
@@ -7727,7 +7795,7 @@ is built from these functions. You can use them to extend Unpoly from your
7727
7795
  up.log.group('Swapping fragment %s', step.selector, function() {
7728
7796
  var swapOptions, swapPromise;
7729
7797
  swapOptions = u.merge(options, u.only(step, 'origin', 'reveal'));
7730
- fixScripts(step.$new);
7798
+ responseDoc.prepareForInsertion(step.$new);
7731
7799
  swapPromise = swapElements(step.$old, step.$new, step.pseudoClass, step.transition, swapOptions);
7732
7800
  return swapPromises.push(swapPromise);
7733
7801
  });
@@ -7749,55 +7817,6 @@ is built from these functions. You can use them to extend Unpoly from your
7749
7817
  cascade = new up.ExtractCascade(selector, options);
7750
7818
  return cascade.bestMatchingSteps();
7751
7819
  };
7752
- fixScripts = function($element) {
7753
- var fix, fixes, i, len, results;
7754
- fixes = [];
7755
- detectScriptFixes($element.get(0), fixes);
7756
- results = [];
7757
- for (i = 0, len = fixes.length; i < len; i++) {
7758
- fix = fixes[i];
7759
- results.push(fix());
7760
- }
7761
- return results;
7762
- };
7763
- detectScriptFixes = function(element, fixes) {
7764
- var child, clone, i, len, ref, results;
7765
- if (element.tagName === 'NOSCRIPT') {
7766
- clone = document.createElement('noscript');
7767
- clone.textContent = element.innerHTML;
7768
- return fixes.push(function() {
7769
- return element.parentNode.replaceChild(clone, element);
7770
- });
7771
- } else if (element.tagName === 'SCRIPT') {
7772
- return fixes.push(function() {
7773
- return element.parentNode.removeChild(element);
7774
- });
7775
- } else {
7776
- ref = element.children;
7777
- results = [];
7778
- for (i = 0, len = ref.length; i < len; i++) {
7779
- child = ref[i];
7780
- results.push(detectScriptFixes(child, fixes));
7781
- }
7782
- return results;
7783
- }
7784
- };
7785
- parseResponseDoc = function(html) {
7786
- var htmlElement;
7787
- htmlElement = u.createElementFromHtml(html);
7788
- return {
7789
- title: function() {
7790
- var ref;
7791
- return (ref = htmlElement.querySelector("head title")) != null ? ref.textContent : void 0;
7792
- },
7793
- first: function(selector) {
7794
- var child;
7795
- if (child = $.find(selector, htmlElement)[0]) {
7796
- return $(child);
7797
- }
7798
- }
7799
- };
7800
- };
7801
7820
  updateHistoryAndTitle = function(options) {
7802
7821
  options = u.options(options, {
7803
7822
  historyMethod: 'push'