Added a dark mode color scheme.
[WebKit-https.git] / Websites / webkit.org / wp-content / themes / webkit / css-status.php
1 <?php
2 /**
3  * Template Name: CSS 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 || xhrRequest.status == 0) {
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
32 var origin = new URL("https://<?php echo strpos(WP_HOST, "webkit.org") !== false ? "svn.webkit.org" : WP_HOST; ?>/");
33 var loadCSSProperties = xhrPromise(new URL("/repository/webkit/trunk/Source/WebCore/css/CSSProperties.json", origin));
34
35 </script>
36
37 <style>
38 :root {
39     --feature-rule-color: hsl(0, 0%, 89.4%);
40     --status-color: hsl(0, 0%, 60%);
41     --supported-color: hsl(100, 100%, 30%);
42     --non-standard-color: hsl(275.4, 77.7%, 35.1%);
43     --in-development-color: hsl(24.5, 91.3%, 50.6%);
44     --no-active-development-color: hsl(240, 60.6%, 59.2%);
45     --partially-supported-color: hsl(180, 25%, 43.9%);
46     --experimental-color: hsl(211.3, 100%, 50%);
47     --under-consideration-color: hsl(5.9, 40.2%, 60%);
48     --removed-not-considering-color: hsl(0, 0%, 49.8%);
49     --not-implemented-color: hsl(0, 0%, 29.8%);
50     --obsolete-color: hsl(50, 100%, 25.1%);
51 }
52
53 @media(prefers-color-scheme:dark) {
54     :root {
55         --feature-rule-color: hsl(0, 0%, 20%);
56         --status-color: hsl(0, 0%, 51%);
57         --supported-color: hsl(79.5, 45.3%, 52%);
58         --non-standard-color: hsl(276.7, 36.3%, 51.4%);
59         --in-development-color: hsl(24.5, 91.3%, 50.6%);
60         --no-active-development-color: hsl(240, 60.6%, 59.2%);
61         --partially-supported-color: hsl(180, 30%, 52%);
62         --exoerimental-color: hsl(211.3, 100%, 50%);
63         --under-consideration-color: hsl(0, 35%, 61%);
64         --removed-not-considering-color: hsl(0, 0%, 49.8%);
65         --not-implemented-color: hsl(0, 0%, 70.2%);
66         --obsolete-color: hsl(31.9, 20.5%, 33.1%);
67     }
68 }
69
70 .feature-status-page {
71     animation: none !important; /* This animation can trigger a hit-testing bug, so remove it for now */
72 }
73
74 .page {
75     display: -webkit-flex;
76     display: flex;
77     flex-direction: column;
78     -webkit-justify-content: space-between;
79     justify-content: space-between;
80     box-sizing: border-box;
81     width: 100%;
82 }
83
84 .page h1 {
85     font-size: 4.2rem;
86     font-weight: 500;
87     line-height: 6rem;
88     margin: 3rem auto;
89     width: 100%;
90     text-align: center;
91 }
92
93 .page h1 a {
94     color: inherit;
95 }
96
97 .page h2 {
98     font-weight: 200;
99     font-size: 3rem;
100 }
101
102 .page h3 {
103     font-weight: 400;
104     font-size: 2.2rem;
105 }
106
107 .css-feature-page {
108     padding-bottom: 3rem;
109 }
110
111 .css-feature-page p {
112     max-width: 920px;
113     margin: 0 auto 3rem;
114 }
115
116 /* Feature Filters */
117 .feature-filters {
118     background-color: hsl(0, 0%, 0%);
119     background-color: var(--figure-mattewhite-background-color);
120     width: 100vw;
121     left: 50%;
122     position: relative;
123     transform: translate(-50vw, 0);
124     box-sizing: border-box;
125     margin-bottom: 3rem;
126     border: 1px solid hsl(0, 0%, 90.6%);
127     border-color: var(--article-border-color);
128     border-left: none;
129     border-right: none;
130 }
131
132 .feature-filters .search-input {
133     background-repeat: no-repeat;
134     background-position-x: 0.5rem;
135     background-position-y: 1rem;
136     background-size: 2rem;
137     padding: 1rem;
138     padding-left: 3rem;
139     padding-right: 8.5rem;
140     font-size: 2rem;
141     width: 100%;
142     margin-top: 0rem;
143     margin-bottom: 0rem;
144     box-sizing: border-box;
145     border-color: transparent;
146 }
147
148 .feature-filters li {
149     display: inline-block;
150     white-space: no-wrap;
151 }
152
153 .property-status label,
154 .feature-filters label {
155     display: table-cell;
156     padding: 0.5rem 1rem;
157     border-style: solid;
158     border-width: 1px;
159     border-radius: 3px;
160     cursor: pointer;
161     float: right;
162     line-height: 1;
163     font-size: 1.6rem;
164 }
165
166 #status-filters {
167     display: none;
168     text-align: center;
169     margin-top: 1rem;
170     margin-bottom: 0;
171 }
172
173 .property-filters {
174     max-width: 920px;
175     margin: 0 auto 0;
176     position: relative;
177     top: 0;
178 }
179
180 .property-filters.opened {
181     margin-top: 1.5rem;
182 }
183
184 .property-filters.opened #status-filters {
185     display: block;
186 }
187
188 #status-filters label {
189     margin-left: 1rem;
190     margin-bottom: 1rem;
191     float: none;
192     display: inline-block;
193     position: relative;
194 }
195
196 .feature-filters label {
197     float: none;
198     display: inline-block;
199 }
200
201 .status-filters {
202     list-style: none;
203     display: inline-block;
204     text-align: center;
205 }
206
207 .filter-toggle:checked + .filter-status {
208     color: hsl(240, 1.3%, 84.5%);
209     color: var(--text-color);
210 }
211
212 .filter-status,
213 .feature-status {
214     color: hsl(0, 0%, 60%);
215     color: var(--status-color);
216     border-color: hsl(0, 0%, 60%);
217     border-color: var(--status-color);
218 }
219
220 .feature-status a {
221     color: inherit;
222 }
223
224 .status-filter,
225 .status-marker {
226     border-color: hsl(0, 0%, 60%);
227     border-color: var(--status-color)
228 }
229
230 .filter-toggle:checked + .filter-status {
231     background-color: hsl(0, 0%, 60%);
232     background-color: var(--status-color);
233 }
234
235 .property-description.status-marker {
236     border-left-width: 3px;
237     border-left-style: solid;
238     padding: 0.5rem 0 0.5rem 1rem;
239 }
240
241 .property-count {
242     max-width: 920px;
243     margin: 0 auto 3rem;
244
245     text-align: right;
246     color: hsl(240, 2.3%, 56.7%);
247     color: var(--text-color-coolgray);
248 }
249
250 .property-header > h3:first-of-type {
251     -webkit-flex-grow: 1;
252     flex-grow: 1;
253     margin: 0;
254 }
255
256 .property-header:after {
257     position: relative;
258     width: 2rem;
259     height: 2rem;
260     right: 0;
261     top: 0.5rem;
262     margin-left: 1rem;
263     transition: transform 0.3s ease-out;
264 }
265
266 .properties {
267     padding: 0;
268     max-width: 920px;
269     margin: 0 auto 3rem;
270     border-bottom: 1px solid hsl(0, 0%, 89.4%);
271     border-color: var(--feature-rule-color);
272 }
273
274 .properties .property {
275     position: relative;
276     display: block;
277     max-height: intrinsic;
278     min-height: 3rem;
279     overflow-y: hidden;
280     cursor: pointer;
281     background-color: transparent;
282     border-color: transparent;
283     border-width: 1px;
284     border-style: solid;
285     border-top-color: hsl(0, 0%, 89.4%);
286     border-top-color: var(--feature-rule-color);
287     padding: 0.5rem;
288     line-height: 1.618;
289     transition: background-color 0.3s ease-in;
290 }
291
292 .property.opened {
293     background-color: hsl(0, 0%, 100%);
294     background-color: var(--figure-mattewhite-background-color);
295     border-left-color: hsl(0, 0%, 89.4%);
296     border-left-color: var(--feature-rule-color);
297     border-right-color: hsl(0, 0%, 89.4%);
298     border-right-color: var(--feature-rule-color);
299     max-height: 120rem;
300 }
301
302 .property.opened .property-header:after {
303     -webkit-transform: rotateX(-180deg);
304     -moz-transform: rotateX(-180deg);
305     transform: rotateX(-180deg);
306     perspective: 600;
307 }
308
309 .property-description .toggleable {
310     display: none;
311 }
312
313 .property.opened .property-description .toggleable {
314     display: block;
315     margin-top: 1rem;
316 }
317
318 .comment {
319     font-size: smaller;
320 }
321
322 .more-info {
323     margin-top: 0.5em;
324     font-size: smaller;
325 }
326
327 .sub-features {
328     font-size: 1.5rem;
329     color: #555;
330 }
331
332 .sub-features ul {
333     list-style: none;
334     display: inline-block;
335     padding: 0;
336     margin: 0;
337 }
338
339 .sub-features li {
340     display: inline;
341 }
342
343 .sub-features li:after {
344     content: ", ";
345 }
346
347 .sub-features li:last-child:after {
348     content: "";
349 }
350
351 ul.values {
352     margin-left: 3em;
353     margin-bottom: 0.5em;
354     cursor: default;
355 }
356
357 .values li.hidden {
358     color: #444;
359 }
360
361 .property-header {
362     position: relative;
363     display: -webkit-flex;
364     display: flex;
365     -webkit-flex-direction: row;
366     flex-direction: row;
367 }
368
369 .property-header .toggle {
370     display: inline-block;
371     background: url('images/menu-down.svg') no-repeat 50%;
372     background-size: 2rem;
373     border: none;
374     width: 2rem;
375     height: 2rem;
376     position: absolute;
377     right: 0;
378     top: 0.5rem;
379     transition: transform 0.3s ease-out;
380 }
381
382 .property.opened .property-header .toggle {
383     transform: rotateX(-180deg);
384 }
385
386 .property-header h3 .spec-label ,
387 .property-header h3 .spec-label a {
388     text-decoration: none;
389     font-weight: 200;
390     color: hsl(0, 0%, 33.3%);
391     color: var(--text-color-medium);
392 }
393
394 .spec-label::before {
395     content: ' — ';
396 }
397
398 .property-description .toggleable {
399     color: hsl(0, 0%, 20%);
400     color: var(--text-color);
401 }
402
403 .property-header h3,
404 .property-header a[name] {
405     color: hsl(0, 0%, 26.7%);
406     color: var(--text-color-heading);
407 }
408
409 .property-alias {
410     font-size: smaller;
411 }
412
413 .property-header p {
414     margin-top: 0.5rem;
415     margin-bottom: 0.5rem;
416 }
417
418 .property-alias,
419 .value-alias,
420 .value-status {
421     color: hsl(240, 2.3%, 56.7%);
422     color: var(--text-color-coolgray);
423 }
424
425 .property.is-hidden {
426     display: none;
427 }
428
429 ul.property-details {
430     margin: 0;
431 }
432 .property-statusItem {
433     margin-right: 0.5em;
434 }
435
436
437 .property-status,
438 .property-status a {
439     color: #999;
440 }
441
442 .property .status-marker {
443     border-left-width: 3px;
444     border-left-style: solid;
445     padding: 0.5rem 0 0.5rem 1rem;
446 }
447
448
449 .status-marker {
450     border-color: hsl(0, 0%, 60%);
451     border-color: var(--status-color)
452 }
453
454 .supported {
455     color: hsl(100, 100%, 30%);
456     color: var(--supported-color);
457     border-color: hsl(100, 100%, 30%);
458     border-color: var(--supported-color);
459 }
460
461 .in-development {
462     color: hsl(24.5, 91.3%, 50.6%);
463     color: var(--in-development-color);
464     border-color: hsl(24.5, 91.3%, 50.6%);
465     border-color: var(--in-development-color);
466 }
467
468 .under-consideration {
469     color: hsl(5.9, 40.2%, 60%);
470     color: var(--under-consideration-color);
471     border-color: hsl(5.9, 40.2%, 60%);
472     border-color: var(--under-consideration-color);
473 }
474
475 .no-active-development {
476     color: hsl(240, 60.6%, 59.2%);
477     color: var(--no-active-development-color);
478     border-color: hsl(240, 60.6%, 59.2%);
479     border-color: var(--no-active-development-color);
480 }
481
482 .experimental {
483     color: hsl(211.3, 100%, 50%);
484     color: var(--experimental-color);
485     border-color: hsl(211.3, 100%, 50%);
486     border-color: var(--experimental-color);
487 }
488
489 .partial-support {
490     color: hsl(180, 25%, 43.9%);
491     color: var(--partially-supported-color);
492     border-color: hsl(180, 25%, 43.9%);
493     border-color: var(--partially-supported-color);
494 }
495
496 .non-standard {
497     color: hsl(275.4, 77.7%, 35.1%);
498     color: var(--non-standard-color);
499     border-color: hsl(275.4, 77.7%, 35.1%);
500     border-color: var(--non-standard-color);
501 }
502
503 .removed,
504 .not-considering {
505     color: hsl(0, 0%, 49.8%);
506     color: var(--removed-not-considering-color);
507     border-color: hsl(0, 0%, 49.8%);
508     border-color: var(--removed-not-considering-color);
509 }
510
511 .not-implemented {
512     color: hsl(0, 0%, 29.8%);
513     color: var(--not-implemented-color);
514     border-color: hsl(0, 0%, 29.8%);
515     border-color: var(--not-implemented-color);
516 }
517
518 .obsolete {
519     color: hsl(50, 100%, 25.1%);
520     color: var(--obsolete-color);
521     border-color: hsl(50, 100%, 25.1%);
522     border-color: var(--obsolete-color);
523 }
524
525 .by-specification {
526     background-color: hsl(0, 0%, 96.9%);
527     background-color: var(--content-background-color);
528     border-color: hsl(0, 0%, 83.9%);
529     border-color: var(--input-border-color);
530 }
531
532 .property-filters.opened .search-input {
533     border-color: hsl(0, 0%, 83.9%);
534     border-color: var(--input-border-color);
535 }
536
537 .feature-filters .filters-toggle-button {
538     background-repeat: no-repeat;
539     background-size: 2rem;
540     background-position: right;
541     background-filter: lightness(2);
542     position: absolute;
543     padding-right: 2.5rem;
544     right: 1rem;
545     top: 1rem;
546     border: none;
547     color: hsl(240, 2.3%, 56.7%);
548 }
549
550 .feature-filters .filters-toggle-button:hover {
551     filter: brightness(0);
552 }
553
554 #filters-toggle {
555     display: none;
556 }
557
558 .property-filters ul {
559     margin-top: 0.5rem;
560 }
561
562 .property-filters ul li {
563     margin-bottom: 0.5rem;
564 }
565
566 .property-filters label > input {
567     position: relative;
568     top: -1px;
569 }
570
571 .prefixes {
572     display: none;
573 }
574
575 #specifications {
576     display: inline-block;
577     font-size: 1.6rem;
578     color: hsl(0, 0%, 20%);
579     color: var(--text-color);
580     margin-left: 1rem;
581 }
582
583 .filter-by-specifications-toggle {
584     position: absolute;
585     display: none;
586     left: 1000rem;
587     top: 0;
588     width: 100%;
589     height: 100%;
590 }
591
592 #specifications:disabled + .filter-by-specifications-toggle {
593     display: block;
594     top: 0;
595     left: 0;
596 }
597
598 h3 a[name], .admin-bar h3 a[name] {
599     top: initial;
600     width: auto;
601     display: inline-block;
602     visibility: visible; /* Override visibility:hidden from themes/webkit/style.css */
603 }
604
605 .pagination:after {
606     display: none;
607 }
608
609 .pagination,
610 .pagination + h1 {
611     margin-top: 0;
612 }
613
614 @media only screen and (max-width: 1180px) {
615     .feature-filters .filters-toggle-button {
616         right: 3rem;
617     }
618 }
619
620 @media only screen and (max-width: 508px) {
621     #property-filters,
622     #property-list {
623         width: 100%;
624     }
625
626     #property-filters {
627         padding-left: 2rem;
628         padding-right: 2rem;
629     }
630
631     .property-header h3 {
632         font-size: 2rem;
633         padding-right: 0.5rem;
634     }
635
636     .property-status {
637         font-size: 1.6rem;
638         margin-top: 0.4rem;
639         float: left;
640     }
641
642     .property-header:after {
643         width: 1rem;
644         height: 1rem;
645         background-size: 1rem;
646         top: 1rem;
647     }
648
649     .property h3 {
650         font-size: 2rem;
651         padding-top: 4rem;
652     }
653
654     .property-header .property-status {
655         font-size: 1.6rem;
656         position: absolute;
657         text-align: left;
658     }
659
660     .property .moreinfo {
661         flex-wrap: wrap;
662     }
663
664     .property .moreinfo .contact {
665         text-align: left;
666     }
667
668     .status-filters {
669         flex-basis: 100%;
670     }
671
672     .status-filters label {
673         margin-left: 0;
674         margin-right: 1rem;
675     }
676 }
677
678 @media(prefers-color-scheme:dark) {
679     .property-header:after {
680         filter: invert(1);
681     }
682
683     .search-input:hover,
684     .search-input:focus,
685     .feature-filters .filters-toggle-button:hover {
686         filter: brightness(2);
687     }
688 }
689
690 </style>
691     <?php if (have_posts()) : while (have_posts()) : the_post(); ?>
692
693         <div class="page css-feature-page" id="post-<?php the_ID(); ?>">
694
695             <div class="connected pagination">
696                 <?php wp_nav_menu( array('theme_location'  => 'feature-subnav') ); ?>
697             </div>
698
699             <h1><a href="<?php echo get_permalink() ?>" rel="bookmark" title="Permanent Link: <?php the_title(); ?>"><?php the_title(); ?></a></h1>
700
701             <section class="feature-filters">
702                 <form id="property-filters" class="property-filters page-width">
703                     <input type="text" id="search" class="search-input" placeholder="Search CSS Features&hellip;" title="Filter the property list." required><label class="filters-toggle-button">Filters</label>
704                     <ul id="status-filters">
705                     </ul>
706
707                     <div class="prefixes">
708                         <h2>Filter by Prefix</h2>
709                         <ul id="prefix-filters">
710                         </ul>
711                     </div>
712
713                 </form>
714             </section>
715
716             <section class="primary">
717                 <div id="property-list">
718                     <div class="property-count">
719                         <p><span id="property-count"></span> <span id="property-pluralize">properties</span></p>
720                     </div>
721                 </div>
722
723                 <template id="success-template">
724                     <ul class="properties" id="properties-container"></ul>
725
726                     <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>
727                     <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>
728                 </template>
729
730                 <template id="error-template">
731                     <p>Error: unable to load the features list (<span id="error-message"></span>).</p>
732                     <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>
733                 </template>
734
735             </section>
736         </div>
737
738     <?php //comments_template(); ?>
739
740     <?php endwhile; else: ?>
741
742         <p>No posts.</p>
743
744     <?php endif; ?>
745
746
747 <script>
748 function initializeStatusPage() {
749
750     const statusOrder = [
751         'supported',
752         'in-development',
753         'under-consideration',
754         'experimental',
755         'non-standard',
756         'not-considering',
757         'not-implemented',
758         'obsolete',
759         'removed',
760     ];
761
762     const readableStatus = {
763         'supported': 'Supported',
764         'in-development': 'In Development',
765         'under-consideration': 'Under Consideration',
766         'experimental': 'Experimental',
767         'non-standard': 'Non-standard',
768         'not-considering': 'Not considering',
769         'not-implemented': 'Not implemented',
770         'obsolete': 'Obsolete',
771         'removed': 'Removed',
772     };
773
774     function sortAlphabetically(array)
775     {
776         function replaceDashPrefix(name)
777         {
778             if (name[0] == '-')
779                 return 'Z' + name.slice(1);
780             return name;
781         }
782
783         array.sort(function(a, b) {
784             // Sort the prefixed properties to the end.
785             var aName = replaceDashPrefix(a.name.toLowerCase());
786             var bName = replaceDashPrefix(b.name.toLowerCase());
787
788             var nameCompareResult = aName.localeCompare(bName);
789
790             if (nameCompareResult)
791                 return nameCompareResult;
792
793             // Status sort
794             var aStatus = a.status != undefined ? a.status.status.toLowerCase() : '';
795             var bStatus = b.status != undefined ? b.status.status.toLowerCase() : '';
796
797             return aStatus.localeCompare(bStatus);
798         });
799     }
800
801     function propertyNameAliases(propertyObject)
802     {
803         if ('codegen-properties' in propertyObject && 'aliases' in propertyObject['codegen-properties'])
804             return propertyObject['codegen-properties'].aliases;
805
806         return [];
807     }
808
809     function propertyLonghands(propertyObject)
810     {
811         if ('codegen-properties' in propertyObject && 'longhands' in propertyObject['codegen-properties'])
812             return propertyObject['codegen-properties'].longhands;
813
814         return [];
815     }
816
817     const prefixRegexp = /^(-webkit-|-epub-|-apple-)(.+)$/;
818
819     function createPropertyView(categoryObject, propertyObject)
820     {
821         function createLinkWithHeading(elementName, heading, linkText, linkUrl)
822         {
823             var container = document.createElement(elementName);
824             if (heading) {
825                 container.textContent = heading + ": ";
826             }
827             var link = document.createElement("a");
828             link.textContent = linkText;
829             link.href = linkUrl;
830             container.appendChild(link);
831             return container;
832         }
833
834         function appendValueWithLink(container, value, link)
835         {
836             if (link) {
837                 var anchor = document.createElement('a');
838                 anchor.href = link;
839                 anchor.textContent = value;
840                 container.appendChild(anchor);
841                 return;
842             }
843
844             container.textContent = value;
845         }
846
847         function makeTwitterLink(twitterHandle)
848         {
849             if (twitterHandle[0] == "@")
850                 twitterHandle = twitterHandle.substring(1);
851             return "https://twitter.com/" + twitterHandle;
852         }
853
854         var hasSpecificationObject = "specification" in propertyObject;
855         var specificationObject = propertyObject.specification;
856
857         var container = document.createElement('li');
858
859         container.className = "property";
860
861         var slug = propertyObject.name.toLowerCase().replace(/ /g, '-');
862         container.setAttribute("id", "property-" + slug);
863
864         var descriptionContainer = document.createElement('div');
865         descriptionContainer.className = "property-description status-marker";
866
867         var featureHeaderContainer = document.createElement('div');
868         featureHeaderContainer.className = "property-header";
869         descriptionContainer.appendChild(featureHeaderContainer);
870
871         var titleElement = document.createElement("h3");
872         var anchorLinkElement = document.createElement("a");
873         anchorLinkElement.href = "#" + container.getAttribute("id");
874         anchorLinkElement.name = container.getAttribute("id");
875         anchorLinkElement.textContent = propertyObject.name;
876         titleElement.appendChild(anchorLinkElement);
877
878         if (categoryObject) {
879             if (specificationObject && ("url" in specificationObject || "obsolete-url" in specificationObject)) {
880                 var url = ("url" in specificationObject) ? specificationObject.url : specificationObject['obsolete-url'];
881                 var specSpan = createLinkWithHeading("span", null, categoryObject.shortname, specificationObject.url);
882                 specSpan.className = 'spec-label';
883                 titleElement.appendChild(specSpan);
884             } else {
885                 var labelSpan = document.createElement("span");
886                 labelSpan.className = 'spec-label';
887                 labelSpan.textContent = categoryObject.shortname;
888                 titleElement.appendChild(labelSpan);
889             }
890         }
891
892         featureHeaderContainer.appendChild(titleElement);
893
894         var toggledContentContainer = document.createElement('div');
895         toggledContentContainer.className = "toggleable";
896         descriptionContainer.appendChild(toggledContentContainer);
897
898         var aliases = propertyNameAliases(propertyObject);
899         if (aliases.length) {
900             var propertyAliasDiv = document.createElement('div');
901             propertyAliasDiv.className = 'property-alias';
902             propertyAliasDiv.textContent = 'Also supported as: ' + aliases.join(', ');
903             toggledContentContainer.appendChild(propertyAliasDiv);
904         }
905
906         var longhands = propertyLonghands(propertyObject);
907         if (longhands.length) {
908             var longhandsDiv = document.createElement('div');
909             longhandsDiv.className = 'longhands';
910
911             longhandsDiv.textContent = 'Shorthand for ';
912
913             for (var i in longhands) {
914                 if (i > 0)
915                     longhandsDiv.appendChild(document.createTextNode(', '));
916                 var longhand = longhands[i];
917                 var longhandLink = document.createElement("a");
918                 longhandLink.href = "#property-" + longhand;
919                 longhandLink.textContent = longhand;
920                 longhandsDiv.appendChild(longhandLink);
921             }
922
923             toggledContentContainer.appendChild(longhandsDiv);
924         }
925
926         function collapsePrefixedValues(values)
927         {
928             var remainingValues = [];
929             var prefixMap = {};
930
931             for (var valueObj of values) {
932                 var valueName = valueObj.value;
933
934                 var result = prefixRegexp.exec(valueName);
935                 if (result) {
936                     var unprefixed = result[2];
937                     var unprefixedValue = findValueByName(values, unprefixed);
938                     if (unprefixedValue) {
939                         (prefixMap[unprefixedValue.value] = prefixMap[unprefixedValue.value] || []).push(valueName);
940                         continue;
941                     }
942                 }
943
944                 remainingValues.push(valueObj);
945             }
946
947             for (var prefixed in prefixMap) {
948                 var unprefixedValue = findValueByName(remainingValues, prefixed);
949                 unprefixedValue.aliases = prefixMap[prefixed];
950             }
951
952             return remainingValues;
953         }
954
955         if (propertyObject.values.length) {
956             var valuesHeader = document.createElement("h4");
957             valuesHeader.textContent = 'Supported Values:';
958             toggledContentContainer.appendChild(valuesHeader);
959
960             var valuesList = document.createElement("ul");
961             valuesList.className = 'values';
962
963             var values = collapsePrefixedValues(propertyObject.values);
964             for (var valueObj of values) {
965                 var li = document.createElement("li");
966
967                 valueObj.el = li;
968
969                 var link = undefined;
970                 var valueAliases = undefined;
971                 var status = undefined;
972                 if ('aliases' in valueObj)
973                     valueAliases = valueObj.aliases;
974
975                 if ('status' in valueObj)
976                     status = valueObj.status;
977
978                 if ('url' in valueObj)
979                     link = valueObj['url'];
980
981                 appendValueWithLink(li, valueObj.value, link);
982
983                 if (valueAliases) {
984                     var span = document.createElement('span');
985                     span.textContent = ' (' + valueAliases.join(', ') + ')';
986                     span.className = 'value-alias';
987                     li.appendChild(span);
988                 }
989
990                 if (status) {
991                     var span = document.createElement('span');
992                     span.textContent = ' (' + status + ')';
993                     span.className = 'value-status';
994                     li.appendChild(span);
995                 }
996
997                 valuesList.appendChild(li);
998             }
999             toggledContentContainer.appendChild(valuesList);
1000         }
1001
1002         var statusContainer = document.createElement("div");
1003         descriptionContainer.classList.add(propertyObject.status.status);
1004         statusContainer.className = "property-status " + propertyObject.status.status;
1005         var statusLabel = document.createElement("label");
1006
1007         if ("webkit-url" in propertyObject) {
1008             var statusLink = document.createElement("a");
1009             statusLink.href = propertyObject["webkit-url"];
1010             statusLink.textContent = readableStatus[propertyObject.status.status];
1011             statusLabel.appendChild(statusLink);
1012         } else {
1013             statusLabel.textContent = readableStatus[propertyObject.status.status];
1014         }
1015         statusContainer.appendChild(statusLabel);
1016         featureHeaderContainer.appendChild(statusContainer);
1017
1018         var toggle = document.createElement('button');
1019         toggle.className = 'toggle';
1020
1021         container.addEventListener('click', function (e) {
1022             container.classList.toggle('opened');
1023         });
1024
1025         featureHeaderContainer.appendChild(toggle);
1026
1027         if (specificationObject && "description" in specificationObject) {
1028             var testDescription = document.createElement('p');
1029             testDescription.className = "property-desc";
1030             testDescription.innerHTML = specificationObject.description;
1031             toggledContentContainer.appendChild(testDescription);
1032         }
1033
1034         if (specificationObject && "comment" in specificationObject) {
1035             if ("description" in specificationObject) {
1036                 var hr = document.createElement("hr");
1037                 hr.className = 'comment';
1038                 toggledContentContainer.appendChild(hr);
1039             }
1040             var comment = document.createElement('p');
1041             comment.className = 'comment';
1042             comment.innerHTML = specificationObject.comment;
1043             toggledContentContainer.appendChild(comment);
1044         }
1045
1046         if (propertyObject.status && "comment" in propertyObject.status) {
1047             var comment = document.createElement('p');
1048             comment.className = 'comment';
1049             comment.innerHTML = propertyObject.status.comment;
1050             toggledContentContainer.appendChild(comment);
1051         }
1052
1053         container.appendChild(descriptionContainer);
1054
1055         function getMostSpecificProperty(categoryObject, specificationObject, attributeName)
1056         {
1057             // The url in the specification object is more specific, so use it if present.
1058             if (specificationObject && attributeName in specificationObject)
1059                 return specificationObject[attributeName];
1060
1061             return categoryObject[attributeName];
1062         }
1063
1064         var hasReferenceLink = categoryObject && "url" in categoryObject;
1065         var hasDocumentationLink = (specificationObject && "documentation-url" in specificationObject) || (categoryObject && "documentation-url" in categoryObject);
1066         var hasContactObject = specificationObject && "contact" in specificationObject;
1067
1068         if (hasDocumentationLink || hasReferenceLink || hasContactObject) {
1069             var moreInfoList = document.createElement("ul");
1070             moreInfoList.className = 'more-info';
1071             if (hasDocumentationLink) {
1072                 // The url in the specification object is more specific, so use it if present.
1073                 var url = getMostSpecificProperty(categoryObject, specificationObject, 'documentation-url');
1074                 moreInfoList.appendChild(createLinkWithHeading("li", "Documentation", url, url));
1075             }
1076
1077             if (hasReferenceLink) {
1078                 var url = getMostSpecificProperty(categoryObject, specificationObject, 'url');
1079                 moreInfoList.appendChild(createLinkWithHeading("li", "Reference", url, url));
1080
1081                 if ('obsolete-url' in specificationObject){
1082                     var url = specificationObject['obsolete-url'];
1083                     moreInfoList.appendChild(createLinkWithHeading("li", "Reference", url, url));
1084                 }
1085             }
1086
1087             if (hasContactObject) {
1088                 var li = document.createElement("li");
1089                 li.textContent = "Contact: ";
1090                 var contactObject = specificationObject.contact;
1091                 if (contactObject.twitter) {
1092                     li.appendChild(createLinkWithHeading("span", null, contactObject.twitter, makeTwitterLink(contactObject.twitter)));
1093                 }
1094                 if (contactObject.email) {
1095                     if (contactObject.twitter) {
1096                         li.appendChild(document.createTextNode(" - "));
1097                     }
1098                     var emailText = contactObject.email;
1099                     if (contactObject.name) {
1100                         emailText = contactObject.name;
1101                     }
1102                     li.appendChild(createLinkWithHeading("span", null, emailText, "mailto:" + contactObject.email));
1103                 }
1104                 moreInfoList.appendChild(li);
1105             }
1106
1107             toggledContentContainer.appendChild(moreInfoList);
1108         }
1109
1110         return container;
1111     }
1112
1113     function canonicalizeIdentifier(identifier)
1114     {
1115         return identifier.toLocaleLowerCase().replace(/ /g, '-');
1116     }
1117
1118     function renderSpecifications(categories, properties, selectedSpecifications)
1119     {
1120         var specificationsList = document.getElementById('specifications');
1121         specificationsList.addEventListener('change', function() { updateSearch(properties); });
1122
1123         var selectedIndex = -1;
1124         var allCategories = Object.keys(categories).sort();
1125
1126         for (var i = 0; i < allCategories.length; ++i) {
1127             var categoryKey = allCategories[i];
1128             var category = categories[categoryKey];
1129             categoryKey = canonicalizeIdentifier(categoryKey);
1130
1131             var option = document.createElement("option");
1132             option.setAttribute('value', categoryKey);
1133             if (selectedSpecifications.indexOf(categoryKey) != -1)
1134                 selectedIndex = i;
1135
1136             option.appendChild(document.createTextNode(" " + category['shortname']));
1137             specificationsList.appendChild(option);
1138         }
1139         if (selectedIndex != -1)
1140             specificationsList.selectedIndex = selectedIndex;
1141     }
1142
1143     function getPropertyCategory(propertyObject)
1144     {
1145         if ('specification' in propertyObject && 'category' in propertyObject.specification)
1146             return propertyObject.specification.category;
1147
1148         return undefined;
1149     }
1150
1151     function renderProperties(categories, propertyObjects)
1152     {
1153         var propertiesContainer = document.getElementById('properties-container');
1154         for (var propertyObject of propertyObjects) {
1155             var category = getPropertyCategory(propertyObject);
1156             propertiesContainer.appendChild(createPropertyView(categories[category], propertyObject));
1157         }
1158     }
1159
1160     function convertToTitleCase(string)
1161     {
1162         return string.replace(/\w\S*/g, function(txt){
1163             return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
1164         });
1165     }
1166
1167     function initSearch(properties, categories)
1168     {
1169         var filtersForm = document.getElementById('property-filters');
1170         var filtersToggleButton = document.getElementsByClassName('filters-toggle-button')[0];
1171         var statusContainer = document.getElementById('status-filters');
1172         var inputField = document.getElementById('search');
1173         var featuresEls = document.querySelectorAll('.properties > li');
1174         var statusFilters = {};
1175
1176         properties.forEach(function(property, i) {
1177             property.el = featuresEls[i];
1178             property.visible = true;
1179
1180             // FIXME: need status per value as well.
1181             if (property.status != undefined) {
1182                 propertyStatusKey = property.status.status.toLocaleLowerCase();
1183
1184                 if (!statusFilters[propertyStatusKey])
1185                     statusFilters[propertyStatusKey] = property.status.status;
1186
1187                 if (statusOrder.indexOf(propertyStatusKey) == -1)
1188                     window.console.log('Status ' + propertyStatusKey + ' is not one of the predefined status keys ', statusOrder);
1189             }
1190         });
1191
1192         var selectedStatuses = statusesFromURL();
1193         var selectedSpecs = specificationsFromURL();
1194
1195         for (var key of statusOrder) {
1196             var status = statusFilters[key];
1197             var canonicalStatus = canonicalizeIdentifier(status);
1198
1199             var entry = document.createElement("li");
1200             var label = document.createElement("label");
1201             var input = document.createElement("input");
1202             input.setAttribute('type','checkbox');
1203             input.setAttribute('value', canonicalStatus);
1204             if (selectedStatuses.indexOf(canonicalStatus) != -1) {
1205                 filtersForm.classList.add('opened');
1206                 input.checked = true;
1207             }
1208             input.className = 'status-checkbox';
1209             input.addEventListener('change', function() { updateSearch(properties); });
1210             label.appendChild(input);
1211             label.className = "status-filter " + canonicalStatus;
1212             label.appendChild(document.createTextNode(" " + readableStatus[status]));
1213             entry.appendChild(label);
1214             statusContainer.appendChild(entry);
1215         }
1216
1217         // Append the special "By Specification" checkbox
1218         {
1219             var entry = document.createElement("li");
1220             var label = document.createElement("label");
1221             var input = document.createElement("input");
1222             input.id = 'by-spec-checkbox';
1223             input.setAttribute('type','checkbox');
1224             if (selectedSpecs.length > 0)
1225                 input.checked = true;
1226             input.addEventListener('change', function() { updateSearch(properties); });
1227             label.appendChild(input);
1228             label.className = "status-filter by-specification";
1229             label.appendChild(document.createTextNode(" By Specification:"));
1230
1231             var specsList = document.createElement('select');
1232             specsList.className = 'specifications';
1233             specsList.id = 'specifications';
1234             specsList.addEventListener('mousedown', function() {
1235                 input.setAttribute('checked','checked');
1236                 input.checked = true;
1237                 console.log(input);
1238                 input.checked = true;
1239             });
1240             var specsListToggle = document.createElement('div');
1241             specsListToggle.className = 'filter-by-specifications-toggle';
1242             label.appendChild(specsList);
1243             label.appendChild(specsListToggle);
1244             entry.appendChild(label);
1245
1246             statusContainer.appendChild(entry);
1247         }
1248
1249         filtersToggleButton.addEventListener('mousedown', function (e) {
1250             filtersForm.classList.toggle('opened');
1251         });
1252
1253         var searchTerm = searchTermFromURL();
1254         if (searchTerm.length) {
1255             inputField.value = searchTerm;
1256             inputField.placeholder = '';
1257         }
1258         inputField.addEventListener('input', function() { updateSearch(properties); });
1259
1260         var inputs = [].slice.call(filtersForm.getElementsByTagName('input'));
1261         inputs.forEach(function (input,i) {
1262             input.addEventListener('click', function (e) {
1263                 e.stopPropagation();
1264             });
1265         });
1266
1267         renderSpecifications(categories, properties, selectedSpecs);
1268     }
1269
1270     function getValuesOfCheckedItems(items)
1271     {
1272         var checkedValues = [];
1273         items.forEach(function(item,i) {
1274             if (item.checked)
1275                 checkedValues.push(item.value);
1276         });
1277
1278         return checkedValues;
1279     }
1280
1281     function getValuesOfSelectedItems(select)
1282     {
1283         var selectedValues = [];
1284
1285         if (select.selectedIndex != -1)
1286             selectedValues.push(select.options[select.selectedIndex].value);
1287
1288         return selectedValues;
1289     }
1290
1291     function selectedSpecifications()
1292     {
1293         var specificationsList = document.getElementById('specifications');
1294         if (!document.getElementById('by-spec-checkbox').checked) {
1295             specificationsList.disabled = true;
1296             return [];
1297         }
1298         specificationsList.disabled = false;
1299         return getValuesOfSelectedItems(specificationsList);
1300     }
1301
1302     function updateSearch(properties)
1303     {
1304         var inputField = document.getElementById('search');
1305         var statusContainer = document.getElementById('status-filters');
1306
1307         var searchTerm = inputField.value.trim().toLowerCase();
1308         var activeStatusFilters = getValuesOfCheckedItems([].slice.call(statusContainer.querySelectorAll('.status-checkbox')));
1309
1310         var prefixContainer = document.getElementById('prefix-filters');
1311         var activePrefixFilters = getValuesOfCheckedItems([].slice.call(prefixContainer.getElementsByTagName('input')));
1312
1313         var numVisible = searchProperties(properties, searchTerm, selectedSpecifications(), activeStatusFilters, activePrefixFilters);
1314         document.getElementById('property-pluralize').textContent = numVisible == 1 ? 'property' : 'properties';
1315         document.getElementById('property-count').textContent = numVisible;
1316
1317         updateSpecsState();
1318         updateURL(searchTerm, selectedSpecifications(), activeStatusFilters, activePrefixFilters);
1319     }
1320
1321     function updateSpecsState()
1322     {
1323         var specsEnabled = document.getElementById('by-spec-checkbox').checked;
1324         var specificationsList = document.getElementById('specifications');
1325
1326         var radiobuttons = [].slice.call(specificationsList.getElementsByTagName('input'));
1327         radiobuttons.forEach(function(radiobutton,i) {
1328             radiobutton.disabled = !specsEnabled;
1329         });
1330     }
1331
1332     function updateURL(searchTerm, selectedSpecifications, activeStatusFilters, activePrefixFilters)
1333     {
1334         var searchString = '';
1335
1336         function appendDelimiter()
1337         {
1338             searchString += searchString.length ? '&' : '?';
1339         }
1340
1341         if (searchTerm.length > 0) {
1342             appendDelimiter();
1343             searchString += 'search=' + encodeURIComponent(searchTerm);
1344         }
1345
1346         if (activeStatusFilters.length) {
1347             appendDelimiter();
1348             searchString += 'status=' + activeStatusFilters.join(',');
1349         }
1350
1351         if (selectedSpecifications.length) {
1352             appendDelimiter();
1353             searchString += 'specs=' + selectedSpecifications.join(',');
1354         }
1355
1356         if (activePrefixFilters.length) {
1357             appendDelimiter();
1358             searchString += 'prefix=' + activePrefixFilters.join(',');
1359         }
1360
1361         var current = window.location.href;
1362         window.location.href = current.replace(/#(.*)$/, '') + '#' + searchString;
1363     }
1364
1365     function searchTermFromURL()
1366     {
1367         var search = window.location.search;
1368         var searchRegExp = /\#.*search=([^&]+)/;
1369
1370         var result;
1371         if (result = window.location.href.match(searchRegExp))
1372             return decodeURIComponent(result[1]);
1373
1374         return '';
1375     }
1376
1377     function statusesFromURL()
1378     {
1379         var search = window.location.search;
1380         var statusRegExp = /\#.*status=([^&]+)/;
1381
1382         var result;
1383         if (result = window.location.href.match(statusRegExp))
1384             return result[1].split(',');
1385
1386         return [];
1387     }
1388
1389     function specificationsFromURL()
1390     {
1391         var search = window.location.search;
1392         var specsRegExp = /\#.*specs=([^&]+)/;
1393
1394         var result;
1395         if (result = window.location.href.match(specsRegExp))
1396             return result[1].split(',');
1397
1398         return [];
1399     }
1400
1401     function valueOrAliasIsPrefixed(valueObj)
1402     {
1403         if (prefixRegexp.exec(valueObj.value))
1404             return true;
1405
1406         if ('alias' in valueObj) {
1407             for (var alias of valueObj.aliases) {
1408                 if (prefixRegexp.exec(alias))
1409                     return true;
1410             }
1411         }
1412
1413         return false;
1414     }
1415
1416     function filterValues(propertyObject, searchTerm, categories, statusFilters)
1417     {
1418         for (var valueObj of propertyObject.values) {
1419             if (!valueObj.el)
1420                 continue;
1421
1422             var visible = false;
1423             visible = valueObj.value.toLowerCase().indexOf(searchTerm) !== -1;
1424
1425             if (!visible) {
1426                 for (var currValueObj of propertyObject.values) {
1427                     if (valueOrAliasIsPrefixed(currValueObj)) {
1428                         visible = true;
1429                         break;
1430                     }
1431                 }
1432             }
1433
1434             if (!visible) {
1435                 for (var currValueObj of propertyObject.values) {
1436                     if (prefixRegexp.exec(currValueObj.value)) {
1437                         visible = true;
1438                         break;
1439                     }
1440                 }
1441             }
1442
1443             if (visible)
1444                 valueObj.el.classList.remove('hidden');
1445             else
1446                 valueObj.el.classList.add('hidden');
1447         }
1448     }
1449
1450     function searchProperties(properties, searchTerm, categories, statusFilters, prefixFilters)
1451     {
1452         var visibleCount = 0;
1453         properties.forEach(function(propertyObject) {
1454             var matchesStatusSearch = isStatusFiltered(propertyObject, statusFilters);
1455             var matchesPrefixSearch = isPrefixFiltered(propertyObject, prefixFilters);
1456
1457             var visible = propertyIsSearchMatch(propertyObject, searchTerm) && isCategoryMatch(propertyObject, categories) && matchesStatusSearch && matchesPrefixSearch;
1458             if (visible && !propertyObject.visible)
1459                 propertyObject.el.className = 'property';
1460             else if (!visible && propertyObject.visible)
1461                 propertyObject.el.className = 'property is-hidden';
1462
1463             if (visible) {
1464                 filterValues(propertyObject, searchTerm);
1465                 ++visibleCount;
1466             }
1467
1468             propertyObject.visible = visible;
1469         });
1470
1471         return visibleCount;
1472     }
1473
1474     function propertyIsSearchMatch(propertyObject, searchTerm)
1475     {
1476         if (searchTerm.length == 0)
1477             return true;
1478
1479         if (propertyObject.name.toLowerCase().indexOf(searchTerm) !== -1)
1480             return true;
1481
1482         if ('keywords' in propertyObject) {
1483             for (var keyword of propertyObject.keywords) {
1484                 if (keyword.toLowerCase().indexOf(searchTerm) !== -1)
1485                     return true;
1486             }
1487         }
1488
1489         for (var valueObj of propertyObject.values) {
1490             if (valueObj.value.toLowerCase().indexOf(searchTerm) !== -1)
1491                 return true;
1492         }
1493
1494         return false;
1495     }
1496
1497     function getSpecificationCategory(propertyObject)
1498     {
1499         if ('specification' in propertyObject) {
1500             var specification = propertyObject.specification;
1501             if ('category' in specification) {
1502                 return specification.category;
1503             }
1504         }
1505         return undefined;
1506     }
1507
1508     function getSpecificationObsoleteCategory(propertyObject)
1509     {
1510         if ('specification' in propertyObject) {
1511             var specification = propertyObject.specification;
1512             if ('obsolete-category' in specification) {
1513                 return specification['obsolete-category'];
1514             }
1515         }
1516         return undefined;
1517     }
1518
1519     function isCategoryMatch(propertyObject, categories)
1520     {
1521         if (!categories.length)
1522             return true;
1523
1524         var category;
1525         if (category = getSpecificationCategory(propertyObject)) {
1526             if (categories.indexOf(category) !== -1) {
1527                 return true;
1528             }
1529         }
1530
1531         if (category = getSpecificationObsoleteCategory(propertyObject)) {
1532             if (categories.indexOf(category) !== -1) {
1533                 return true;
1534             }
1535         }
1536         return false;
1537     }
1538
1539     function propertyOrAliasIsPrefixed(propertyObject)
1540     {
1541         if (prefixRegexp.exec(propertyObject.name))
1542             return true;
1543
1544         for (var alias of propertyNameAliases(propertyObject)) {
1545             if (prefixRegexp.exec(alias))
1546                 return true;
1547         }
1548
1549         return false;
1550     }
1551
1552     function isStatusFiltered(propertyObject, activeFilters)
1553     {
1554         if (activeFilters.length == 0)
1555             return true;
1556         if (propertyObject.status === undefined)
1557             return false;
1558         if (activeFilters.indexOf(propertyObject.status.status) !== -1)
1559             return true;
1560
1561         return false;
1562     }
1563
1564     function isPrefixFiltered(propertyObject, activeFilters)
1565     {
1566         if (activeFilters.length == 0)
1567             return true;
1568
1569         if (activeFilters.indexOf('prefix-only-property') !== -1)
1570             return prefixRegexp.exec(propertyObject.name);
1571
1572         if (activeFilters.indexOf('prefix-supported-property') !== -1)
1573             return propertyOrAliasIsPrefixed(propertyObject);
1574
1575         if (activeFilters.indexOf('prefix-supported-value') !== -1) {
1576             for (var valueObj of propertyObject.values) {
1577                 if (valueOrAliasIsPrefixed(valueObj))
1578                     return true;
1579             }
1580         }
1581
1582         if (activeFilters.indexOf('prefix-only-value') !== -1) {
1583             for (var valueObj of propertyObject.values) {
1584                 if (prefixRegexp.exec(valueObj.value))
1585                     return true;
1586             }
1587         }
1588
1589         return false;
1590     }
1591
1592     function findValueByName(values, name)
1593     {
1594         return values.find(function(element) {
1595             return element.value === name;
1596         })
1597     }
1598
1599     function mergeProperties(unprefixedPropertyObj, prefixedPropertyObj)
1600     {
1601         (unprefixedPropertyObj['codegen-properties'].aliases = unprefixedPropertyObj['codegen-properties'].aliases || []).push(prefixedPropertyObj.name);
1602
1603         for (var valueObj of prefixedPropertyObj.values) {
1604             if (!findValueByName(unprefixedPropertyObj.values, valueObj.value))
1605                 prefixedPropertyObj.values.push(valueObj);
1606         }
1607
1608         return unprefixedPropertyObj;
1609     }
1610
1611     // Sometimes we have separate entries for -webkit-foo and foo.
1612     function collapsePrefixedProperties(properties)
1613     {
1614         function findPropertyByName(properties, name)
1615         {
1616             return properties.find(function(element) {
1617                 return element.name === name;
1618             })
1619         }
1620
1621         var remainingProperties = [];
1622         var prefixMap = {};
1623
1624         for (var propertyObj of properties) {
1625             var propertyName = propertyObj.name;
1626
1627             var result = prefixRegexp.exec(propertyName);
1628             if (result) {
1629                 var unprefixed = result[2];
1630                 var unprefixedProperty = findPropertyByName(properties, unprefixed);
1631                 if (unprefixedProperty) {
1632                     mergeProperties(unprefixedProperty, propertyObj);
1633                     continue;
1634                 }
1635             }
1636
1637             remainingProperties.push(propertyObj);
1638         }
1639
1640         return remainingProperties;
1641     }
1642
1643     function canonicalizeValues(propertyObject)
1644     {
1645         var valueObjects = [];
1646         // Convert all values to objects.
1647         if ('values' in propertyObject) {
1648             for (var value of propertyObject.values) {
1649                 if (typeof value === 'object')
1650                     valueObjects.push(value);
1651                 else
1652                     valueObjects.push({ 'value' : value });
1653             }
1654         }
1655         propertyObject.values = valueObjects;
1656     }
1657
1658     function canonicalizeStatus(propertyObject, categories)
1659     {
1660         // Inherit "status" from the category if not explicitly specified.
1661         if (!('status' in propertyObject)) {
1662             var category = getSpecificationCategory(propertyObject)
1663             if (category) {
1664                 var categoryObject = categories[category];
1665                 if (categoryObject) {
1666                     if ('status' in categoryObject) {
1667                         propertyObject.status = {
1668                             'status' : categoryObject.status
1669                         };
1670                     }
1671                 }
1672             }
1673         } else {
1674             // Convert all values to objects.
1675             if (typeof propertyObject.status === 'string')
1676                 propertyObject.status = { 'status': propertyObject.status };
1677         }
1678
1679         if (!('status' in propertyObject)) {
1680             propertyObject.status = {
1681                 'status' : 'supported',
1682                 'enabled-by-default' : true
1683             };
1684         } else if (!('status' in propertyObject.status))
1685             propertyObject.status.status = 'supported';
1686
1687         propertyObject.status.status = canonicalizeIdentifier(propertyObject.status.status);
1688     }
1689
1690     function renderContent(results)
1691     {
1692         var mainContent = document.getElementById("property-list");
1693         var successSubtree = document.importNode(document.getElementById("success-template").content, true);
1694         mainContent.appendChild(successSubtree);
1695
1696         var properties = results[0]['properties'];
1697         var everythingToShow = [];
1698
1699         var categories = results[0]['categories'];
1700
1701         for (var property in properties) {
1702             var propertyObject = properties[property];
1703             propertyObject.name = property;
1704
1705             canonicalizeValues(propertyObject);
1706             canonicalizeStatus(propertyObject, categories);
1707
1708             everythingToShow.push(propertyObject);
1709         }
1710
1711         everythingToShow = collapsePrefixedProperties(everythingToShow);
1712         sortAlphabetically(everythingToShow);
1713
1714         renderProperties(categories, everythingToShow);
1715
1716         initSearch(everythingToShow, categories);
1717
1718         updateSearch(everythingToShow);
1719     }
1720
1721     function displayError(error)
1722     {
1723         window.console.log('displayError', error)
1724         var mainContent = document.getElementById("property-list");
1725         var successSubtree = document.importNode(document.getElementById("error-template").content, true);
1726
1727         var errorMessage = "Unable to load " + error.url;
1728
1729         if (error.request.status !== 200) {
1730             errorMessage += ", status: " + error.request.status + " - " + error.request.statusText;
1731         } else if (!error.response) {
1732             errorMessage += ", the JSON file cannot be processed.";
1733         }
1734
1735         successSubtree.querySelector("#error-message").textContent = errorMessage;
1736
1737         mainContent.appendChild(successSubtree);
1738     }
1739
1740     Promise.all([loadCSSProperties]).then(renderContent).catch(displayError);
1741 }
1742
1743 document.addEventListener("DOMContentLoaded", initializeStatusPage);
1744 </script>
1745
1746 <?php get_footer(); ?>