62a87aab945fbc01338ea9be82846c8e686a9309
[WebKit-https.git] / Websites / webkit.org / wp-content / themes / webkit / status.php
1 <?php
2 /**
3  * Template Name: Status Page
4  **/
5 ?>
6 <?php get_header(); ?>
7 <script>
8 function xhrPromise(url) {
9     return new Promise(function(resolve, reject) {
10         var xhrRequest = new XMLHttpRequest();
11         xhrRequest.open('GET', url, true);
12         xhrRequest.responseType = "json";
13
14         xhrRequest.onload = function() {
15             if (xhrRequest.status == 200) {
16                 if (xhrRequest.response) {
17                     resolve(xhrRequest.response);
18                 } else {
19                     reject({ request: xhrRequest, url:url});
20                 }
21             } else {
22                 reject({ request: xhrRequest, url:url});
23             }
24         };
25         xhrRequest.onerror = function() {
26             reject({ request: xhrRequest, url:url});
27         };
28         xhrRequest.send();
29     });
30 }
31 var origin = new URL("https://<?php echo strpos(WP_HOST, "webkit.org") !== false ? "svn.webkit.org" : WP_HOST; ?>/");
32 var loadJavaScriptCoreFeatures = xhrPromise(new URL("/repository/webkit/trunk/Source/JavaScriptCore/features.json", origin));
33 var loadWebCoreFeatures = xhrPromise(new URL("/repository/webkit/trunk/Source/WebCore/features.json", origin));
34 </script>
35
36 <style>
37
38 .page h1 {
39     font-size: 4.2rem;
40     font-weight: 500;
41     line-height: 6rem;
42     margin: 3rem auto;
43     width: 100%;
44     text-align: center;
45     color: #333333;
46 }
47
48 .page h1 a {
49     color: inherit;
50 }
51
52 .feature-status-page {
53     padding-bottom: 3rem;
54 }
55
56 .feature-status-page p {
57     max-width: 920px;
58     margin: 0 auto 3rem;
59 }
60
61 .feature-filters {
62     background-color: #ffffff;
63     width: 100vw;
64     left: 50%;
65     position: relative;
66     transform: translate(-50vw, 0);
67     box-sizing: border-box;
68     margin-bottom: 3rem;
69     border: 1px solid #DDDDDD;
70     border-left: none;
71     border-right: none;
72 }
73
74 .feature-filters form {
75     padding-top: 3rem;
76     padding-bottom: 3rem;
77     display: flex;
78     flex-wrap: wrap;
79 }
80
81 input[type=text].search-input {
82     margin-bottom: 1rem;
83     width: 100%;
84     flex: 1;
85 }
86
87 .feature-filters li {
88     display: inline-block;
89 }
90
91 .feature-status label,
92 .feature-filters label {
93     display: table-cell;
94     padding: 0.5rem 1rem;
95     border-style: solid;
96     border-width: 1px;
97     border-radius: 3px;
98     cursor: pointer;
99     float: right;
100     line-height: 1;
101     font-size: 1.6rem;
102 }
103
104 .status-filters label {
105     margin-left: 1rem;
106 }
107
108 .status-filters {
109     list-style: none;
110     display: inline-block;
111     text-align: right;
112     flex: 2;
113     flex-grow: 1;
114 }
115
116 .filter-toggle:checked + .filter-status {
117     color: #ffffff;
118 }
119
120 .filter-status,
121 .feature-status {
122     color: #999999;
123     border-color: #999999;
124 }
125
126 .feature-status a {
127     color: inherit;
128 }
129
130 .filter-status,
131 .status-marker {
132     border-color: #999999;
133 }
134 .filter-toggle:checked + .filter-status {
135     background-color: #999999;
136 }
137
138 .supported {
139     color: #339900;
140     border-color: #339900;
141 }
142
143 .filter-toggle:checked + .supported {
144     background-color: #339900;
145 }
146
147 .supported-in-preview {
148     color: #66149f;
149     border-color: #66149f;
150 }
151
152 .filter-toggle:checked + .supported-in-preview {
153     background-color: #66149f;
154 }
155
156 .in-development {
157     color: #f46c0e;
158     border-color: #f46c0e;
159 }
160 .filter-toggle:checked + .in-development {
161     background-color: #f46c0e;
162 }
163
164 .no-active-development {
165     color: #5858D6;
166     border-color: #5858D6;
167 }
168
169 .filter-toggle:checked + .no-active-development {
170     background-color: #5858D6;
171 }
172
173 .partially-supported  {
174     color: #548c8c;
175     border-color: #548c8c;
176 }
177
178 .filter-toggle:checked + .partially-supported {
179     background-color: #548c8c;
180 }
181
182 .prototyping {
183     color: #007AFF;
184     border-color: #007AFF;
185 }
186
187 .filter-toggle:checked + .prototyping {
188     background-color: #007AFF;
189 }
190
191 .under-consideration {
192     color: #c27870;
193     border-color: #c27870;
194 }
195
196 .filter-toggle:checked + .under-consideration {
197     background-color: #c27870;
198 }
199
200 .feature.is-hidden {
201     display: none;
202 }
203
204 .features {
205     max-width: 920px;
206     margin: 0 auto 3rem;
207     border-bottom: 1px solid #e4e4e4;
208
209 }
210
211 .feature-count {
212     max-width: 920px;
213     margin: 0 auto 3rem;
214
215     text-align: right;
216     color: #999;
217 }
218
219 .feature-count p {
220     margin: 0;
221 }
222
223 .feature {
224     border-color: transparent;
225     border-width: 1px;
226     border-style: solid;
227     border-top-color: #e4e4e4;
228     padding: 0.5rem;
229     line-height: 1.618;
230     transition: background-color 0.3s ease-out;
231 }
232
233 .feature-header {
234     font-weight: 400;
235     font-size: 2.5rem;
236     display: flex;
237 }
238
239 .feature-header h3 {
240     flex: 1;
241     flex-grow: 2;
242     padding-right: 1rem;
243     box-sizing: border-box;
244 }
245
246 .feature-header h3 a {
247     padding-right: 1rem;
248 }
249
250 .feature-header .feature-status {
251     flex: 2;
252     text-align: right;
253     font-size: 2rem;
254 }
255
256 .feature-container.status-marker {
257     border-left-width: 3px;
258     border-left-style: solid;
259     padding: 0.5rem 0 0.5rem 1rem;
260 }
261
262 .feature-header a[name] {
263     color: #444444;
264 }
265
266 .feature-header .internal-reference {
267     display: inline-block;
268     font-size: 1.6rem;
269     font-weight: 600;
270     white-space: nowrap;
271 }
272
273 .feature-header .internal-reference a {
274     color: #999999;
275 }
276
277 .feature-header:after {
278     position: relative;
279     width: 2rem;
280     height: 2rem;
281     right: 0;
282     top: 0.5rem;
283     margin-left: 1rem;
284     transition: transform 0.3s ease-out;
285 }
286
287 .feature-details {
288     display: none;
289     width: 50%;
290 }
291
292 .feature.opened {
293     background: #ffffff;
294     border-left-color: #e4e4e4;
295     border-right-color: #e4e4e4;
296 }
297
298 .feature.opened .feature-details {
299     display: block;
300 }
301
302 .feature h4 {
303     color: #999999;
304     font-weight: 600;
305     margin-top: 1rem;
306     margin-bottom: 0;
307 }
308
309 .feature .moreinfo {
310     list-style: none;
311     display: flex;
312     width: 100%;
313 }
314
315 .feature .moreinfo li {
316     flex-grow: 1;
317 }
318
319 .feature .moreinfo .contact {
320     text-align: right;
321 }
322
323 .feature .feature-desc {
324     color: #444444;
325 }
326
327 .feature .comment {
328     color: #666666;
329     font-style: italic;
330 }
331
332 .sub-features {
333     font-size: 1.5rem;
334     color: #555;
335 }
336
337 .sub-features ul {
338     list-style: none;
339     padding: 0;
340     margin: 0;
341 }
342
343 .sub-features li {
344     display: inline-block;
345     white-space: nowrap;
346 }
347
348 .sub-features li:after {
349     content: ", ";
350     white-space: pre;
351 }
352
353 .sub-features li:last-child:after {
354     content: "";
355 }
356
357 @media only screen and (max-width: 1000px) {
358     .feature-details {
359         width: 100%;
360     }
361 }
362
363 @media only screen and (max-width: 508px) {
364     #feature-filters,
365     #feature-list {
366         width: 100%;
367     }
368
369     #feature-filters {
370         padding-left: 2rem;
371         padding-right: 2rem;
372     }
373
374     .feature-header h3 {
375         font-size: 2rem;
376         padding-right: 0.5rem;
377     }
378
379     .feature-status {
380         font-size: 1.6rem;
381         margin-top: 0.4rem;
382         float: left;
383     }
384
385     .feature-header:after {
386         width: 1rem;
387         height: 1rem;
388         background-size: 1rem;
389         top: 1rem;
390     }
391
392     .feature h3 {
393         font-size: 2rem;
394         padding-top: 4rem;
395     }
396
397     .feature-header .feature-status {
398         font-size: 1.6rem;
399         position: absolute;
400         text-align: left;
401     }
402
403     .feature .moreinfo {
404         flex-wrap: wrap;
405     }
406
407     .feature .moreinfo .contact {
408         text-align: left;
409     }
410
411     .status-filters {
412         text-align: left;
413         flex-basis: 100%;
414     }
415
416     .status-filters label {
417         margin-left: 0;
418         margin-right: 1rem;
419     }
420 }
421
422 h3 a[name], .admin-bar h3 a[name] {
423     top: initial;
424     width: auto;
425     display: inline-block;
426     visibility: visible;
427 }
428
429
430 </style>
431         <?php if (have_posts()) : while (have_posts()) : the_post(); ?>
432
433         <div id="content">
434         <div class="page feature-status-page" id="post-<?php the_ID(); ?>">
435                         <h1><a href="<?php echo get_permalink() ?>" rel="bookmark" title="Permanent Link: <?php the_title(); ?>"><?php the_title(); ?></a></h1>
436
437             <div class="feature-filters">
438                 <form id="feature-filters" class="page-width">
439                     <input type="text" id="search" class="search-input" placeholder="Search features&hellip;" title="Filter the feature list." required>
440                     <ul id="status-filters" class="status-filters"></ul>
441                 </form>
442             </div>
443
444             <div id="feature-list">
445                 <div class="feature-count">
446                     <p><span id="feature-count"></span> <span id="feature-pluralize">features</span></p>
447                 </div>
448
449             </div>
450
451             <template id="success-template">
452                 <ul class="features" id="features-container"></ul>
453                 <p>Cannot find something? You can contact <a href="https://twitter.com/webkit">@webkit</a> on Twitter or contact the <a href="https://lists.webkit.org/mailman/listinfo/webkit-help">webkit-help</a> mailing list for questions.</p>
454                 <p>You can also <a href="/contributing-code/">contribute to features</a> directly, the entire project is Open Source. To report bugs on existing features or check existing bug reports, see <a href="https://bugs.webkit.org">https://bugs.webkit.org</a>.</p>
455             </template>
456             <template id="error-template">
457                 <p>Error: unable to load the features list (<span id="error-message"></span>).</p>
458                 <p>If this is not resolved soon, please contact <a href="https://twitter.com/webkit">@webkit</a> on Twitter or the <a href="https://lists.webkit.org/mailman/listinfo/webkit-help">webkit-help</a> mailing list.</p>
459             </template>
460         </div>
461         </div>
462
463         <?php //comments_template(); ?>
464
465         <?php endwhile; else: ?>
466
467                 <p>No posts.</p>
468
469         <?php endif; ?>
470
471
472 <script>
473 function initializeStatusPage() {
474
475     const statusOrder = [
476         'under consideration',
477         'prototyping',
478         'in development',
479         'supported in preview',
480         'partially supported',
481         'supported',
482         'deprecated',
483         'removed',
484         'not considering'
485     ];
486
487     function sortAlphabetically(array)
488     {
489         array.sort(function(a, b){
490             var aName = a.name.toLowerCase();
491             var bName = b.name.toLowerCase();
492
493             var nameCompareResult = aName.localeCompare(bName);
494
495             if ( nameCompareResult )
496                 return nameCompareResult;
497
498             // Status sort
499             var aStatus = a.status != undefined ? a.status.status.toLowerCase() : '';
500             var bStatus = b.status != undefined ? b.status.status.toLowerCase() : '';
501
502             return aStatus.localeCompare(bStatus);
503         });
504     }
505
506     function createFeatureView(featureObject)
507     {
508
509         function createLinkWithHeading(elementName, heading, linkText, linkUrl) {
510             var container = document.createElement(elementName);
511             if (heading) {
512                 var h4 = document.createElement('h4');
513                 h4.textContent = heading;
514                 container.appendChild(h4);
515             }
516             var link = document.createElement("a");
517             link.textContent = linkText;
518             link.href = linkUrl;
519             if (linkText == linkUrl)
520                 link.textContent = link.hostname + "…";
521             container.appendChild(link);
522             return container;
523         }
524
525         function makeTwitterLink(twitterHandle) {
526             if (twitterHandle[0] == "@")
527                 twitterHandle = twitterHandle.substring(1);
528             return "https://twitter.com/" + twitterHandle;
529         }
530
531         var container = document.createElement('li');
532         var hasDocumentationLink = "documentation-url" in featureObject;
533         var hasReferenceLink = "url" in featureObject;
534         var hasContactObject = "contact" in featureObject;
535         var hasSpecificationObject = "specification" in featureObject;
536
537         container.addEventListener('click', function (e) {
538             if ( container.className.indexOf('opened') !== -1 ) {
539                 container.className = container.className.replace(' opened','');
540             } else container.className += " opened";
541         });
542
543         container.className = "feature";
544
545         var slug = canonicalizeIdentifier(featureObject.name);
546         if ("features" in featureObject) {
547             container.setAttribute("id", "specification-" + slug);
548         } else {
549             container.setAttribute("id", "feature-" + slug);
550         }
551
552         if (window.location.hash && window.location.hash == "#" + container.getAttribute('id')) {
553             container.className += " opened";
554         }
555
556         var featureContainer = document.createElement('div');
557         featureContainer.className = "feature-container status-marker";
558
559         var featureHeaderContainer = document.createElement('div');
560         featureHeaderContainer.className = "feature-header";
561         featureContainer.appendChild(featureHeaderContainer);
562
563         var titleElement = document.createElement("h3");
564         var anchorLinkElement = document.createElement("a");
565         anchorLinkElement.href = "#" + container.getAttribute("id");
566         anchorLinkElement.name = container.getAttribute("id");
567         anchorLinkElement.textContent = featureObject.name;
568         titleElement.appendChild(anchorLinkElement);
569
570         // Add sub-feature here
571         if (hasSpecificationObject) {
572             var specification = featureObject.specification;
573             var specSpan = createLinkWithHeading("h4", null, specification.name, "#specification-" + specification.name.toLowerCase().replace(/ /g, '-'));
574             specSpan.className = "internal-reference";
575             titleElement.appendChild(specSpan);
576         }
577
578         featureHeaderContainer.appendChild(titleElement);
579
580         if ("status" in featureObject) {
581             var statusContainer = document.createElement("div");
582             var statusClassName = canonicalizeIdentifier(featureObject.status.status);
583             featureContainer.className += " " + statusClassName;
584             statusContainer.className = "feature-status " + statusClassName;
585             var statusLabel = document.createElement("label");
586
587             if ("webkit-url" in featureObject) {
588                 var statusLink = document.createElement("a");
589                 statusLink.href = featureObject["webkit-url"];
590                 statusLink.textContent = featureObject.status.status;
591                 statusLabel.appendChild(statusLink);
592             } else {
593                 statusLabel.textContent = featureObject.status.status;
594             }
595
596             statusContainer.appendChild(statusLabel);
597             featureHeaderContainer.appendChild(statusContainer);
598         }
599
600         var featureDetails = document.createElement('div');
601         featureDetails.className = 'feature-details';
602
603         if ("description" in featureObject) {
604             var textDescription = document.createElement('p');
605             textDescription.className = "feature-desc";
606             textDescription.innerHTML = featureObject.description;
607             featureDetails.appendChild(textDescription);
608         }
609
610         if ("comment" in featureObject) {
611             var comment = document.createElement('p');
612             comment.className = 'comment';
613             comment.innerHTML = featureObject.comment;
614             featureDetails.appendChild(comment);
615         }
616
617         if ("features" in featureObject && featureObject.features.length) {
618             var internalLinkContainer = document.createElement("div");
619             internalLinkContainer.className = "internal-reference sub-features";
620             var internalHeading = document.createElement("h4");
621             internalHeading.textContent = "Includes";
622             internalLinkContainer.appendChild(internalHeading);
623
624             var list = document.createElement("ul");
625             for (var feature of featureObject.features) {
626                 var link = document.createElement("a");
627                 link.textContent = feature.name;
628                 link.href = "#feature-" + canonicalizeIdentifier(feature.name);
629
630                 var li = document.createElement("li");
631                 li.appendChild(link);
632                 list.appendChild(li);
633             }
634             internalLinkContainer.appendChild(list);
635             featureDetails.appendChild(internalLinkContainer);
636         }
637
638         if (hasDocumentationLink || hasReferenceLink || hasContactObject) {
639             var moreInfoList = document.createElement("ul");
640             moreInfoList.className = 'moreinfo';
641             if (hasDocumentationLink) {
642                 var url = featureObject["documentation-url"];
643                 moreInfoList.appendChild(createLinkWithHeading("li", "Documentation", url, url));
644             }
645
646             if (hasReferenceLink) {
647                 var url = featureObject.url;
648                 moreInfoList.appendChild(createLinkWithHeading("li", "Reference", url, url));
649             }
650
651             if (hasContactObject) {
652                 var li = document.createElement("li");
653                 li.className = "contact";
654                 var contactHeading = document.createElement("h4");
655                 contactHeading.textContent = "Contact";
656                 li.appendChild(contactHeading);
657
658                 if (featureObject.contact.twitter) {
659                     li.appendChild(createLinkWithHeading("span", null, featureObject.contact.twitter, makeTwitterLink(featureObject.contact.twitter)));
660                 }
661                 if (featureObject.contact.email) {
662                     if (featureObject.contact.twitter) {
663                         li.appendChild(document.createTextNode(" - "));
664                     }
665                     var emailText = featureObject.contact.email;
666                     if (featureObject.contact.name) {
667                         emailText = featureObject.contact.name;
668                     }
669                     li.appendChild(createLinkWithHeading("span", null, emailText, "mailto:" + featureObject.contact.email));
670                 }
671                 moreInfoList.appendChild(li);
672             }
673
674             featureDetails.appendChild(moreInfoList);
675         }
676
677         featureContainer.appendChild(featureDetails);
678         container.appendChild(featureContainer);
679
680         return container;
681     }
682
683     function canonicalizeIdentifier(identifier)
684     {
685         return identifier.toLocaleLowerCase().replace(/ /g, '-');
686     }
687
688
689     function renderFeaturesAndSpecifications(featureLikeObjects)
690     {
691         var featureContainer = document.getElementById('features-container');
692         for (var featureLikeObject of featureLikeObjects) {
693             featureContainer.appendChild(createFeatureView(featureLikeObject));
694         }
695     }
696
697     function initSearch(featuresArray)
698     {
699         var filtersForm = document.getElementById('feature-filters');
700         var statusContainer = document.getElementById('status-filters');
701         var inputField = document.getElementById('search');
702         var featuresEls = document.querySelectorAll('.features > li');
703         var statusFilters = {};
704
705         featuresArray.forEach(function(feature, i) {
706             feature.el = featuresEls[i];
707             feature.visible = true;
708
709             if (feature.status != undefined) {
710                 featureStatusKey = feature.status.status.toLocaleLowerCase();
711
712                 if (!statusFilters[featureStatusKey])
713                     statusFilters[featureStatusKey] = feature.status.status;
714
715                 if (statusOrder.indexOf(featureStatusKey) == -1)
716                     window.console.log('Status ' + featureStatusKey + ' is not one of the predefined status keys ', statusOrder);
717
718             }
719         });
720
721         for (var key of statusOrder) {
722             if (statusFilters[key] == undefined)
723                 continue;
724
725             var statusLabel = statusFilters[key];
726             var statusId = canonicalizeIdentifier(statusLabel);
727             var entry = document.createElement("li");
728             var label = document.createElement("label");
729             var input = document.createElement("input");
730
731             input.setAttribute('type','checkbox');
732             input.setAttribute('value', key);
733             input.setAttribute('id', 'toggle-' + statusId);
734             input.className = 'filter-toggle';
735             input.addEventListener('change', function() { updateSearch(featuresArray); });
736
737
738             label.className = "filter-status " + statusId;
739             label.setAttribute('for', 'toggle-' + statusId);
740             label.appendChild(input);
741             label.appendChild(document.createTextNode(" " + statusLabel));
742
743             entry.appendChild(label);
744
745             statusContainer.appendChild(entry);
746         }
747
748         filtersForm.addEventListener('click', function (e) {
749             if ( filtersForm.className.indexOf('opened') !== -1 ) {
750                 filtersForm.className = filtersForm.className.replace(' opened','');
751             } else filtersForm.className += " opened";
752         });
753
754         var searchTerm = searchTermFromURL();
755         if (searchTerm.length) {
756             inputField.value = searchTerm;
757             inputField.placeholder = '';
758         }
759         inputField.addEventListener('input', function() { updateSearch(featuresArray); });
760
761
762         var inputs = [].slice.call(filtersForm.getElementsByTagName('input'));
763         inputs.forEach(function (input,i) {
764             input.addEventListener('click', function (e) {
765                 e.stopPropagation();
766             });
767         });
768
769         function search(ev)
770         {
771             var searchTerm = inputField.value.trim().toLowerCase();
772             var activeStatusFilters = [];
773             var checkboxes = [].slice.call(statusContainer.getElementsByTagName('input'));
774             checkboxes.forEach(function(checkbox,i) {
775                 if ( checkbox.checked )
776                     activeStatusFilters.push(checkbox.value);
777             });
778
779             searchFeatures(featuresArray, searchTerm, activeStatusFilters);
780         }
781     }
782
783     function getValuesOfCheckedItems(items)
784     {
785         var checkedValues = [];
786         items.forEach(function(item,i) {
787             if (item.checked)
788                 checkedValues.push(item.value);
789         });
790
791         return checkedValues;
792     }
793
794     function updateSearch(properties)
795     {
796         var inputField = document.getElementById('search');
797         var statusContainer = document.getElementById('status-filters');
798
799         var searchTerm = inputField.value.trim().toLowerCase();
800         var activeStatusFilters = getValuesOfCheckedItems([].slice.call(statusContainer.querySelectorAll('.filter-toggle')));
801
802         var numVisible = searchFeatures(properties, searchTerm, activeStatusFilters);
803         document.getElementById('feature-pluralize').textContent = numVisible == 1 ? 'feature' : 'features';
804         document.getElementById('feature-count').textContent = numVisible;
805
806         updateURL(searchTerm, activeStatusFilters);
807     }
808
809     function searchFeatures(features, searchTerm, statusFilters)
810     {
811         var visibleCount = 0;
812         features.forEach(function(featureObject) {
813             var matchesStatusSearch = isStatusFiltered(featureObject, statusFilters);
814
815             var visible = isSearchMatch(featureObject, searchTerm) && matchesStatusSearch;
816             if (visible && !featureObject.visible)
817                 featureObject.el.className = 'feature';
818             else if (!visible && featureObject.visible)
819                 featureObject.el.className = 'feature is-hidden';
820
821             if (visible) {
822                 // filterValues(featureObject, searchTerm);
823                 ++visibleCount;
824             }
825
826             featureObject.visible = visible;
827         });
828
829         return visibleCount;
830     }
831
832     function searchTermFromURL()
833     {
834         var search = window.location.search;
835         var searchRegExp = /\#.*search=([^&]+)/;
836
837         var result;
838         if (result = window.location.href.match(searchRegExp))
839             return decodeURIComponent(result[1]);
840
841         return '';
842     }
843
844     function isSearchMatch(feature, searchTerm)
845     {
846         if (feature.name.toLowerCase().indexOf(searchTerm) !== -1)
847             return true;
848         if ("keywords" in feature) {
849             for (var keyword of feature.keywords) {
850                 if (keyword.toLowerCase().indexOf(searchTerm) !== -1)
851                     return true;
852             }
853         }
854         return false;
855     }
856
857     function isStatusFiltered(feature, activeFilters)
858     {
859         if (activeFilters.length == 0)
860             return true;
861         if (feature.status === undefined)
862             return false;
863         if (activeFilters.indexOf(feature.status.status.toLowerCase()) != -1)
864             return true;
865
866         return false;
867     }
868
869     function filterValues(featureObject, searchTerm, statusFilters)
870     {
871         for (var valueObj of featureObject.values) {
872             if (!valueObj.el)
873                 continue;
874
875             var visible = false;
876             visible = valueObj.value.toLowerCase().indexOf(searchTerm) !== -1;
877
878             if (visible)
879                 valueObj.el.classList.remove('hidden');
880             else
881                 valueObj.el.classList.add('hidden');
882         }
883     }
884
885     function displayFeatures(results)
886     {
887         var mainContent = document.getElementById("feature-list");
888         var successSubtree = document.importNode(document.getElementById("success-template").content, true);
889         mainContent.appendChild(successSubtree);
890
891         var allSpecifications = [];
892         for (var i in results) {
893             allSpecifications = allSpecifications.concat(results[i].specification);
894         }
895         var specificationsByName = {}
896         for (var specification of allSpecifications) {
897             specification.features = [];
898             specification.isSpecification = true;
899             specificationsByName[specification.name] = specification;
900         }
901
902         var allFeatures = [];
903         for (var i in results) {
904             allFeatures = allFeatures.concat(results[i].features);
905         }
906         var featuresByName = {};
907         for (var feature of allFeatures) {
908             if ('specification' in feature) {
909                 var featureSpecification = feature.specification;
910                 var specificationObject = specificationsByName[featureSpecification];
911                 if (specificationObject != undefined) {
912                     specificationObject.features.push(feature);
913                     feature.specification = specificationObject;
914                 } else {
915                     feature.specification = {
916                         name: featureSpecification
917                     };
918                 }
919             }
920             feature.isSpecification = false;
921             featuresByName[feature.name] = feature;
922         }
923
924         var everythingToShow = allFeatures.concat(allSpecifications);
925
926         sortAlphabetically(everythingToShow);
927
928         renderFeaturesAndSpecifications(everythingToShow);
929
930         initSearch(everythingToShow);
931
932         updateSearch(everythingToShow);
933
934         if (window.location.hash.length) {
935             var hash = window.location.hash;
936             window.location.hash = ""; // Change hash so navigation takes place
937             window.location.hash = hash;
938         }
939     }
940
941     function displayError(error)
942     {
943         var mainContent = document.getElementById("feature-list");
944         var successSubtree = document.importNode(document.getElementById("error-template").content, true);
945
946         var errorMessage = "Unable to load " + error.url;
947
948         if (error.request.status !== 200) {
949             errorMessage += ", status: " + error.request.status + " - " + error.request.statusText;
950         } else if (!error.response) {
951             errorMessage += ", the JSON file cannot be processed.";
952         }
953
954         successSubtree.querySelector("#error-message").textContent = errorMessage;
955
956         mainContent.appendChild(successSubtree);
957     }
958
959     function updateURL(searchTerm, activeStatusFilters)
960     {
961         var searchString = '';
962
963         function appendDelimiter()
964         {
965             searchString += searchString.length ? '&' : '?';
966         }
967
968         if (searchTerm.length > 0) {
969             appendDelimiter();
970             searchString += 'search=' + encodeURIComponent(searchTerm);
971         }
972
973         if (activeStatusFilters.length) {
974             appendDelimiter();
975             searchString += 'status=' + activeStatusFilters.join(',');
976         }
977
978         if (searchString.length) {
979             var current = window.location.href;
980             window.location.href = current.replace(/\??#(.*)$/, '') + '#' + searchString;
981         }
982
983     }
984
985
986     Promise.all([loadJavaScriptCoreFeatures, loadWebCoreFeatures]).then(displayFeatures).catch(displayError);
987 }
988
989 document.addEventListener("DOMContentLoaded", initializeStatusPage);
990 </script>
991
992 <?php get_footer(); ?>