2b0343ed894962e8786198146154adb102be8516
[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 /*
36     TODO:
37         "r" not labeled as SVG. needs two links (circle and radialGradient)
38         filter on unspecified properties/values
39
40         need separate filters on properties and values
41         
42         background-repeat-x isn't a web-exposed longhand in the background property.
43
44         Need to indicate that some properties apply to SVG and HTML:
45             pointer-events
46             paint-order
47
48         Annotate appearance values.
49         Annotate things that only work in @rules like src or page, size, unicode-range, src, device orientation stuff.
50
51         Denote replacement properties & values (e.g. word-wrap is now overflow-wrap.) other than in comments
52
53         Show status comments
54         Mark things pending removal
55         Add a removed status.
56
57     PROPERTY ISSUES
58         -webkit-backface-visibility is not unprefixed
59         "animatable" wrong for animation properties.
60
61     Mark things as not applicable to the open web:
62             -webkit-border-fit
63             -apple-trailing-word
64             -webkit-column-progress
65             -webkit-column-axis
66
67             -webkit-border-fit?. r19862 for iChat.
68             status of margin-collapse properties
69
70     Mark things as internal-only:
71             -webkit-marquee*
72             -webkit-text-security
73             -webkit-text-decorations-in-effect
74             -webkit-nbsp-mode ?
75             -webkit-font-size-delta?
76 */
77 </script>
78
79 <style>
80
81 .page {
82     display: -webkit-flex;
83     display: flex;
84     -webkit-flex-wrap: wrap;
85     flex-wrap: wrap;
86     -webkit-justify-content: space-between;
87     justify-content: space-between;
88     box-sizing: border-box;
89     width: 100%;
90 }
91
92 .page h1 {
93     font-size: 4.2rem;
94     font-weight: 200;
95     line-height: 6rem;
96     color: black;
97     text-align: left;
98     margin: 3rem auto;
99     width: 100%;
100 }
101
102 .page h1 a {
103     color: #444;
104 }
105
106 .page h2 {
107     font-weight: 200;
108     font-size: 3rem;
109 }
110
111 .page h3 {
112     font-weight: 400;
113     font-size: 2.2rem;
114 }
115
116 .page p {
117     margin-bottom: 3rem;
118 }
119
120 .property-count {
121     text-align: right;
122     color: #999;
123 }
124
125 #property-list {
126     display: inline-block;
127     width: 65%;
128     word-wrap: break-word;
129 }
130
131 /* Hide the internal links on search since they are unlikely to work. */
132 #search:required:valid + *  .internal-reference {
133     display: none;
134 }
135
136 .property-header > h3:first-of-type {
137     margin: 0;
138 }
139
140 ul.properties {
141     padding: 0;
142 }
143
144 .properties .property {
145     position: relative;
146     display: block;
147     background-color: #f9f9f9;
148     border: 1px solid #ddd;
149     border-radius: 3px;
150     padding: 1em;
151     margin: 1em 0 !important;
152     max-height: intrinsic;
153     min-height: 3rem;
154     overflow-y: hidden;
155     cursor: pointer;
156     -webkit-transition: background-color 0.3s ease-in;
157     -moz-transition: background-color 0.3s ease-in;
158     transition: background-color 0.3s ease-in;
159 }
160
161 .properties .property:hover {
162     background-color: white;
163 }
164
165 .property.opened {
166     background-color: white;
167     max-height: 120rem;
168 }
169
170 .property-description .toggleable {
171     display: none;
172 }
173
174 .property.opened .property-description .toggleable {
175     display: block;
176     margin-top: 1rem;
177 }
178
179 .more-info {
180     margin-top: 0.5em;
181 }
182
183 .sub-features {
184     font-size: 1.5rem;
185     color: #555;
186 }
187
188 .sub-features ul {
189     list-style: none;
190     display: inline-block;
191     padding: 0;
192     margin: 0;
193 }
194
195 .sub-features li {
196     display: inline;
197 }
198
199 .sub-features li:after {
200     content: ", ";
201 }
202
203 .sub-features li:last-child:after {
204     content: "";
205 }
206
207 ul.values {
208     margin-left: 3em;
209     margin-top: 0.5em;
210     cursor: default;
211 }
212
213 .values li.hidden {
214     color: #444;
215 }
216
217 .property-header {
218     position: relative;
219     padding-right: 3rem;
220 }
221
222 .property-header:after {
223     display: inline-block;
224     content: "";
225     background: url('images/menu-down.svg') no-repeat 50%;
226     background-size: 2rem;
227     width: 2rem;
228     height: 2rem;
229     position: absolute;
230     right: 0;
231     top: 0.5rem;
232     -webkit-transition: transform 0.3s ease-out;
233     -moz-transition: transform 0.3s ease-out;
234     transition: transform 0.3s ease-out;
235 }
236
237 .property.opened .property-header:after {
238     -webkit-transform: rotateX(-180deg);
239     -moz-transform: rotateX(-180deg);
240     transform: rotateX(-180deg);
241 }
242
243 .property-header h3 .internal-reference a {
244     color: #999;
245     text-decoration: none;
246     padding-left: 0.5em;
247 }
248
249 .property-header h3 .spec-label {
250     color: #999;
251     text-decoration: none;
252     font-weight: 200;
253 }
254
255 .property-header h3 .spec-label a {
256     color: #999;
257     text-decoration: none;
258     font-weight: 200;
259 }
260
261 .spec-label::before {
262     content: ' — ';
263 }
264 .property-header h3 a {
265     color: #444;
266 }
267
268 .property-header .property-alias {
269     font-size: smaller;
270     color: #999;
271 }
272
273 .property-header p {
274     margin-top: 0.5rem;
275     margin-bottom: 0.5rem;
276 }
277
278 .value-alias {
279     color: #999;
280 }
281
282 .value-status {
283     color: #999;
284 }
285
286 .property-header h3 .internal-reference a:hover {
287     color: inherit;
288     text-decoration: underline;
289 }
290
291 .property.is-hidden {
292     display: none;
293 }
294
295 ul.property-details {
296     margin: 0;
297 }
298 .property-statusItem {
299     margin-right: 0.5em;
300 }
301
302 .property-status {
303     font-size: 2rem;
304     display: inline-block;
305     position: relative;
306     min-width: 4em;
307     text-align: right;
308     display: none; /* Hide status for now. */
309 }
310
311 .property-status,
312 .property-status a {
313     color: #999;
314 }
315
316 .property .status-marker {
317     width: 0;
318     height: 0;
319     position: absolute;
320     top: 0;
321     left: 0;
322     border-style: solid;
323     border-width: 20px 20px 0 0;
324     border-color: transparent transparent transparent transparent;
325 }
326
327 #status-filters .done,
328 .property-status.done,
329 .property-status.done a {
330     color: #339900;
331 }
332
333 .status-marker.done {
334     border-color: #339900 transparent transparent transparent;
335 }
336
337 #status-filters .in-development,
338 .property-status.in-development,
339 .property-status.in-development a {
340     color: #f46c0e;
341 }
342
343 .status-marker.in-development {
344     border-color: #f46c0e transparent transparent transparent;
345 }
346
347 #status-filters .no-active-development,
348 .property-status.no-active-development,
349 .property-status.no-active-development a {
350     color: #5858D6;
351 }
352
353 .status-marker.no-active-development {
354     border-color: #5858D6 transparent transparent transparent;
355 }
356
357 #status-filters .partial-support,
358 .property-status.partial-support,
359 .property-status.partial-support a {
360     color: #548c8c;
361 }
362
363 .status-marker.partial-support {
364     border-color: #548c8c transparent transparent transparent;
365 }
366
367 #status-filters .prototyping,
368 .property-status.prototyping,
369 .property-status.prototyping a {
370     color: #007AFF;
371 }
372
373 .status-marker.prototyping {
374     border-color: #007AFF transparent transparent transparent;
375 }
376
377 #status-filters .experimental,
378 .property-status.experimental,
379 .property-status.experimental a {
380     color: #007AFF;
381 }
382
383 .status-marker.experimental {
384     border-color: #007AFF transparent transparent transparent;
385 }
386
387 #status-filters .under-consideration,
388 .property-status.under-consideration,
389 .property-status.under-consideration a {
390     color: #cc9d00;
391 }
392
393 .status-marker.under-consideration {
394     border-color: #FFC500 transparent transparent transparent;
395 }
396
397 .property-status.removed,
398 .property-status.removed a {
399     color: #999;
400 }
401
402 .status-marker.removed {
403     border-color: #999 transparent transparent transparent;
404 }
405
406 #status-filters .non-standard,
407 .property-status.non-standard,
408 .property-status.non-standard a {
409     color: #8000FF;
410 }
411
412 .status-marker.non-standard {
413     border-color: #8000FF transparent transparent transparent;
414 }
415
416 #status-filters .not-considering,
417 .property-status.not-considering,
418 .property-status.not-considering a {
419     color: #7F7F7F;
420 }
421
422 .status-marker.not-considering {
423     border-color: #7F7F7F transparent transparent transparent;
424 }
425
426 #status-filters .not-implemented,
427 .property-status.not-implemented,
428 .property-status.not-implemented a {
429     color: #4C4C4C;
430 }
431
432 .status-marker.not-implemented {
433     border-color: #4C4C4C transparent transparent transparent;
434 }
435
436 #status-filters .obsolete,
437 .property-status.obsolete,
438 .property-status.obsolete a {
439     color: #804000;
440 }
441
442 .status-marker.obsolete {
443     border-color: #804000 transparent transparent transparent;
444 }
445
446
447 sidebar {
448     width: -webkit-calc(33.33% - 3rem);
449     width: -moz-calc(33.33% - 3rem);
450     width: calc(33.33% - 3rem);
451     display: inline-block;
452     margin-right: 3rem;
453     font-size: 2rem;
454     margin-left: 1rem;
455 }
456
457 .property-filters {
458     position: relative;
459     top: 0;
460     margin-top: 0.5em;
461 }
462
463 #search {
464     font-size: 2rem;
465     padding: 1rem;
466     border-radius: 3px;
467     border: 1px solid #cccccc;
468     width: 100%;
469     margin-top: 1.5rem;
470     box-sizing: border-box;
471 }
472
473 .property-filters ul {
474     margin-top: 0.5rem;
475     margin-bottom: 1.5rem;
476 }
477
478 .property-filters ul li {
479     margin-bottom: 0.5rem;
480 }
481
482 .property-filters label > input {
483     position: relative;
484     top: -3px;
485 }
486
487 .prefixes {
488     display: none;
489 }
490
491 #specifications {
492     max-height: 29rem;
493     overflow: auto;
494 }
495
496 #specifications li {
497     font-size: smaller;
498     cursor: hand;
499     margin-bottom: 0.5rem;
500     margin-left: 2rem;
501 }
502
503 h3 a[name], .admin-bar h3 a[name] {
504     top: initial;
505     width: auto;
506     display: inline-block;
507     visibility: visible; /* Override visibility:hidden from themes/webkit/style.css */
508 }
509
510 @media only screen and (max-width: 508px) {
511     #property-filters,
512     #property-list {
513         width: 100%;
514     }
515
516     #property-filters {
517         border: 1px solid #ddd;
518         border-radius: 3px;
519         background: #f6f6f6;
520         padding: 1rem;
521         box-sizing: border-box;
522         margin-right: 0;
523         margin-bottom: 3rem;
524     }
525
526     .property-header h3 {
527         font-size: 2rem;
528     }
529
530     .property-status {
531         font-size: 1.6rem;
532         margin-top: 0.4rem;
533     }
534 }
535
536 </style>
537     <?php if (have_posts()) : while (have_posts()) : the_post(); ?>
538
539         <div class="page feature-status-page" id="post-<?php the_ID(); ?>">
540             <?php echo str_repeat('&nbsp;', 200);?>
541             <h1><a href="<?php echo get_permalink() ?>" rel="bookmark" title="Permanent Link: <?php the_title(); ?>"><?php the_title(); ?></a></h1>
542
543             <sidebar>
544                 <form id="property-filters" class="property-filters">
545                     <h2>Filters</h2>
546                     <input type="text" id="search" placeholder="Search filter&hellip;" title="Filter the property list." required>
547                     <h2>Filter by Status</h2>
548                     <ul id="status-filters">
549                     </ul>
550                 </form>
551
552                 <div class="prefixes">
553                     <h2>Filter by Prefix</h2>
554                     <ul id="prefix-filters">
555                     </ul>
556                 </div>
557             </sidebar>
558
559             <div id="property-list">
560                 <div class="property-count">
561                     <p><span id="property-count"></span> <span id="property-pluralize">properties</span></p>
562                 </div>
563             </div>
564
565             <template id="success-template">
566                 <ul class="properties" id="properties-container"></ul>
567
568                 <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>
569                 <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>
570             </template>
571             <template id="error-template">
572                 <p>Error: unable to load the features list (<span id="error-message"></span>).</p>
573                 <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>
574             </template>
575         </div>
576
577     <?php //comments_template(); ?>
578
579     <?php endwhile; else: ?>
580
581         <p>No posts.</p>
582
583     <?php endif; ?>
584
585
586 <script>
587 function initializeStatusPage() {
588
589     function sortAlphabetically(array)
590     {
591         function replaceDashPrefix(name)
592         {
593             if (name[0] == '-')
594                 return 'Z' + name.slice(1);
595             return name;
596         }
597
598         array.sort(function(a, b) {
599             // Sort the prefixed properties to the end.
600             var aName = replaceDashPrefix(a.name.toLowerCase());
601             var bName = replaceDashPrefix(b.name.toLowerCase());
602
603             var nameCompareResult = aName.localeCompare(bName);
604
605             if (nameCompareResult)
606                 return nameCompareResult;
607
608             // Status sort
609             var aStatus = a.status != undefined ? a.status.status.toLowerCase() : '';
610             var bStatus = b.status != undefined ? b.status.status.toLowerCase() : '';
611
612             return aStatus.localeCompare(bStatus);
613         });
614     }
615
616     function propertyNameAliases(propertyObject)
617     {
618         if ('codegen-properties' in propertyObject && 'aliases' in propertyObject['codegen-properties'])
619             return propertyObject['codegen-properties'].aliases;
620
621         return [];
622     }
623
624     function propertyLonghands(propertyObject)
625     {
626         if ('codegen-properties' in propertyObject && 'longhands' in propertyObject['codegen-properties'])
627             return propertyObject['codegen-properties'].longhands;
628
629         return [];
630     }
631
632     const prefixRegexp = /^(-webkit-|-epub-|-apple-)(.+)$/;
633
634     function createPropertyView(categoryObject, propertyObject)
635     {
636         function createLinkWithHeading(elementName, heading, linkText, linkUrl)
637         {
638             var container = document.createElement(elementName);
639             if (heading) {
640                 container.textContent = heading + ": ";
641             }
642             var link = document.createElement("a");
643             link.textContent = linkText;
644             link.href = linkUrl;
645             container.appendChild(link);
646             return container;
647         }
648         
649         function appendValueWithLink(container, value, link)
650         {
651             if (link) {
652                 var anchor = document.createElement('a');
653                 anchor.href = link;
654                 anchor.textContent = value;
655                 container.appendChild(anchor);
656                 return;
657             }
658             
659             container.textContent = value;
660         }
661
662         function makeTwitterLink(twitterHandle)
663         {
664             if (twitterHandle[0] == "@")
665                 twitterHandle = twitterHandle.substring(1);
666             return "https://twitter.com/" + twitterHandle;
667         }
668
669         var hasSpecificationObject = "specification" in propertyObject;
670         var specificationObject = propertyObject.specification;
671
672         var container = document.createElement('li');
673
674         var hasSpecLink = specificationObject && "url" in specificationObject;
675         var hasReferenceLink = categoryObject && "url" in categoryObject;
676         var hasDocumentationLink = (specificationObject && "documentation-url" in specificationObject) || (categoryObject && "documentation-url" in categoryObject);
677         var hasContactObject = specificationObject && "contact" in specificationObject;
678
679         container.addEventListener('click', function (e) {
680             if (container.className.indexOf('opened') !== -1) {
681                 container.className = container.className.replace(' opened','');
682             } else container.className += " opened";
683         });
684
685         container.className = "property";
686
687         var slug = propertyObject.name.toLowerCase().replace(/ /g, '-');
688         container.setAttribute("id", "property-" + slug);
689
690         if (window.location.hash && window.location.hash == "#" + container.getAttribute('id')) {
691             container.className += " opened";
692         }
693
694         var cornerStatus = document.createElement('div');
695         cornerStatus.className = "status-marker ";
696         container.appendChild(cornerStatus);
697
698         var descriptionContainer = document.createElement('div');
699         descriptionContainer.className = "property-description";
700
701         var featureHeaderContainer = document.createElement('div');
702         featureHeaderContainer.className = "property-header";
703         descriptionContainer.appendChild(featureHeaderContainer);
704
705         var titleElement = document.createElement("h3");
706         var anchorLinkElement = document.createElement("a");
707         anchorLinkElement.href = "#" + container.getAttribute("id");
708         anchorLinkElement.name = container.getAttribute("id");
709         anchorLinkElement.textContent = propertyObject.name;
710         titleElement.appendChild(anchorLinkElement);
711
712         if (categoryObject) {
713             if (hasSpecLink) {
714                 var specSpan = createLinkWithHeading("span", null, categoryObject.shortname, specificationObject.url);
715                 specSpan.className = 'spec-label';
716                 titleElement.appendChild(specSpan);
717             } else {
718                 var labelSpan = document.createElement("span");
719                 labelSpan.className = 'spec-label';
720                 labelSpan.textContent = categoryObject.shortname;
721                 titleElement.appendChild(labelSpan);
722             }
723         }
724
725         featureHeaderContainer.appendChild(titleElement);
726
727         var aliases = propertyNameAliases(propertyObject);
728         if (aliases.length) {
729             var propertyAliasDiv = document.createElement('div');
730             propertyAliasDiv.className = 'property-alias';
731             propertyAliasDiv.textContent = 'Also supported as: ' + aliases.join(', ');
732             featureHeaderContainer.appendChild(propertyAliasDiv);
733         }
734
735         var toggledContentContainer = document.createElement('div');
736         toggledContentContainer.className = "toggleable";
737         featureHeaderContainer.appendChild(toggledContentContainer);
738
739         var longhands = propertyLonghands(propertyObject);
740         if (longhands.length) {
741             var longhandsDiv = document.createElement('div');
742             longhandsDiv.className = 'longhands';
743
744             longhandsDiv.textContent = 'Shorthand for ';
745
746             for (var i in longhands) {
747                 if (i > 0)
748                     longhandsDiv.appendChild(document.createTextNode(', '));
749                 var longhand = longhands[i];
750                 var longhandLink = document.createElement("a");
751                 longhandLink.href = "#property-" + longhand;
752                 longhandLink.textContent = longhand;
753                 longhandsDiv.appendChild(longhandLink);
754             }
755         
756             toggledContentContainer.appendChild(longhandsDiv);
757         }
758         
759         function collapsePrefixedValues(values)
760         {
761             var remainingValues = [];
762             var prefixMap = {};
763             
764             for (var valueObj of values) {
765                 var valueName = valueObj.value;
766
767                 var result = prefixRegexp.exec(valueName);
768                 if (result) {
769                     var unprefixed = result[2];
770                     var unprefixedValue = findValueByName(values, unprefixed);
771                     if (unprefixedValue) {
772                         (prefixMap[unprefixedValue.value] = prefixMap[unprefixedValue.value] || []).push(valueName);
773                         continue;
774                     }
775                 }
776                 
777                 remainingValues.push(valueObj);
778             }
779             
780             for (var prefixed in prefixMap) {
781                 var unprefixedValue = findValueByName(remainingValues, prefixed);
782                 unprefixedValue.aliases = prefixMap[prefixed];
783             }
784             
785             return remainingValues;
786         }
787
788         if (propertyObject.values.length) {
789             var valuesHeader = document.createElement("h4");
790             valuesHeader.textContent = 'Supported Values:';
791             toggledContentContainer.appendChild(valuesHeader);
792             
793             var valuesList = document.createElement("ul");
794             valuesList.className = 'values';
795             
796             var values = collapsePrefixedValues(propertyObject.values);
797             for (var valueObj of values) {
798                 var li = document.createElement("li");
799                 
800                 valueObj.el = li;
801
802                 var link = undefined;
803                 var valueAliases = undefined;
804                 var status = undefined;
805                 if ('aliases' in valueObj)
806                     valueAliases = valueObj.aliases;
807
808                 if ('status' in valueObj)
809                     status = valueObj.status;
810
811                 if ('url' in valueObj)
812                     link = valueObj['url'];
813
814                 appendValueWithLink(li, valueObj.value, link);
815                 
816                 if (valueAliases) {
817                     var span = document.createElement('span');
818                     span.textContent = ' (' + valueAliases.join(', ') + ')';
819                     span.className = 'value-alias';
820                     li.appendChild(span);
821                 }
822                 
823                 if (status) {
824                     var span = document.createElement('span');
825                     span.textContent = ' (' + status + ')';
826                     span.className = 'value-status';
827                     li.appendChild(span);
828                 }
829                 
830                 valuesList.appendChild(li);
831             }
832             toggledContentContainer.appendChild(valuesList);
833         }
834         
835         var statusContainer = document.createElement("span");
836         cornerStatus.className += statusClassName = propertyObject.status.status.toLowerCase().replace(/ /g, '-');
837         statusContainer.className = "property-status " + statusClassName;
838         if ("webkit-url" in propertyObject) {
839             var statusLink = document.createElement("a");
840             statusLink.href = propertyObject["webkit-url"];
841             statusLink.textContent = propertyObject.status.status;
842             statusContainer.appendChild(statusLink);
843         } else {
844             statusContainer.textContent = propertyObject.status.status;
845         }
846
847         toggledContentContainer.appendChild(statusContainer);
848         
849         if (specificationObject && "description" in specificationObject) {
850             var testDescription = document.createElement('p');
851             testDescription.className = "property-desc";
852             testDescription.innerHTML = specificationObject.description;
853             toggledContentContainer.appendChild(testDescription);
854         }
855
856         if (specificationObject && "comment" in specificationObject) {
857             if ("description" in specificationObject) {
858                 var hr = document.createElement("hr");
859                 hr.className = 'comment';
860                 toggledContentContainer.appendChild(hr);
861             }
862             var comment = document.createElement('p');
863             comment.className = 'comment';
864             comment.innerHTML = specificationObject.comment;
865             toggledContentContainer.appendChild(comment);
866         }
867
868         container.appendChild(descriptionContainer);
869         
870         function getMostSpecificProperty(categoryObject, specificationObject, attributeName)
871         {
872             // The url in the specification object is more specific, so use it if present.
873             if (specificationObject && attributeName in specificationObject)
874                 return specificationObject[attributeName];
875
876             return categoryObject[attributeName];
877         }
878
879         if (hasDocumentationLink || hasReferenceLink || hasContactObject) {
880             var moreInfoList = document.createElement("ul");
881             moreInfoList.className = 'more-info';
882             if (hasDocumentationLink) {
883                 // The url in the specification object is more specific, so use it if present.
884                 var url = getMostSpecificProperty(categoryObject, specificationObject, 'documentation-url');
885                 moreInfoList.appendChild(createLinkWithHeading("li", "Documentation", url, url));
886             }
887
888             if (hasReferenceLink) {
889                 var url = getMostSpecificProperty(categoryObject, specificationObject, 'url');
890                 moreInfoList.appendChild(createLinkWithHeading("li", "Reference", url, url));
891             }
892
893             if (hasContactObject) {
894                 var li = document.createElement("li");
895                 li.textContent = "Contact: ";
896                 var contactObject = specificationObject.contact;
897                 if (contactObject.twitter) {
898                     li.appendChild(createLinkWithHeading("span", null, contactObject.twitter, makeTwitterLink(contactObject.twitter)));
899                 }
900                 if (contactObject.email) {
901                     if (contactObject.twitter) {
902                         li.appendChild(document.createTextNode(" - "));
903                     }
904                     var emailText = contactObject.email;
905                     if (contactObject.name) {
906                         emailText = contactObject.name;
907                     }
908                     li.appendChild(createLinkWithHeading("span", null, emailText, "mailto:" + contactObject.email));
909                 }
910                 moreInfoList.appendChild(li);
911             }
912
913             toggledContentContainer.appendChild(moreInfoList);
914         }
915
916         return container;
917     }
918     
919     function renderSpecifications(categories, properties)
920     {
921         var specificationsList = document.getElementById('specifications');
922
923         var allCategories = Object.keys(categories).sort();
924         for (var categoryKey of allCategories) {
925             var category = categories[categoryKey];
926             var entry = document.createElement("li");
927             var label = document.createElement("label");
928             var input = document.createElement("input");
929             input.setAttribute('type','radio');
930             input.setAttribute('value', categoryKey);
931             input.setAttribute('name', 'categories');
932             input.addEventListener('change', function() { updateSearch(properties); });
933             label.appendChild(input);
934             label.className = categoryKey.toLocaleLowerCase().replace(/ /g, '-');
935             label.appendChild(document.createTextNode(" " + category['shortname']));
936             entry.appendChild(label);
937             specificationsList.appendChild(entry);
938         }
939     }
940     
941     function getPropertyCategory(propertyObject)
942     {
943         if ('specification' in propertyObject && 'category' in propertyObject.specification)
944             return propertyObject.specification.category;
945
946         return undefined;
947     }
948
949     function renderProperties(categories, propertyObjects)
950     {
951         var propertiesContainer = document.getElementById('properties-container');
952         for (var propertyObject of propertyObjects) {
953             var category = getPropertyCategory(propertyObject);
954             propertiesContainer.appendChild(createPropertyView(categories[category], propertyObject));
955         }
956     }
957
958     function convertToTitleCase(string)
959     {
960         return string.replace(/\w\S*/g, function(txt){
961             return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
962         });
963     }
964
965     function initSearch(properties, categories)
966     {
967         var filtersForm = document.getElementById('property-filters');
968         var statusContainer = document.getElementById('status-filters');
969         var inputField = document.getElementById('search');
970         var featuresEls = document.querySelectorAll('.properties > li');
971         var statusFilters = {};
972
973         var statusOrder = [
974             'done',
975             'in development',
976             'under consideration',
977             'experimental',
978             'non-standard',
979             'not considering',
980             'not implemented',
981             'obsolete',
982         ];
983
984         properties.forEach(function(property, i) {
985             property.el = featuresEls[i];
986             property.visible = true;
987
988             // FIXME: need status per value as well.
989             if (property.status != undefined) {
990                 propertyStatusKey = property.status.status.toLocaleLowerCase();
991
992                 if (!statusFilters[propertyStatusKey])
993                     statusFilters[propertyStatusKey] = property.status.status;
994
995                 if (statusOrder.indexOf(propertyStatusKey) == -1)
996                     window.console.log('Status ' + propertyStatusKey + ' is not one of the predefined status keys ', statusOrder);
997             }
998         });
999         
1000         for (var key of statusOrder) {
1001             var status = statusFilters[key];
1002             var entry = document.createElement("li");
1003             var label = document.createElement("label");
1004             var input = document.createElement("input");
1005             input.setAttribute('type','checkbox');
1006             input.setAttribute('value', key);
1007             input.className = 'status-checkbox';
1008             input.addEventListener('change', function() { updateSearch(properties); });
1009             label.appendChild(input);
1010             label.className = status.replace(/ /g, '-');
1011             label.appendChild(document.createTextNode(" " + convertToTitleCase(status)));
1012             entry.appendChild(label);
1013             statusContainer.appendChild(entry);
1014         }
1015         
1016         // Append the special "By Specification" checkbox
1017         {
1018             var entry = document.createElement("li");
1019             var label = document.createElement("label");
1020             var input = document.createElement("input");
1021             input.id = 'by-spec-checkbox';
1022             input.setAttribute('type','checkbox');
1023             input.addEventListener('change', function() { updateSearch(properties); });
1024             label.appendChild(input);
1025             label.appendChild(document.createTextNode(" By Specification:"));
1026             entry.appendChild(label);
1027             
1028             var specsList = document.createElement('ul');
1029             specsList.className = 'specifications';
1030             specsList.id = 'specifications';
1031             entry.appendChild(specsList);
1032             
1033             statusContainer.appendChild(entry);
1034         }
1035
1036         var prefixContainer = document.getElementById('prefix-filters');
1037         var prefixFilters = {};
1038         prefixFilters['prefix-supported-property'] = 'Property with and without prefix';
1039         prefixFilters['prefix-only-property'] = 'Prefixed-only Properties';
1040         prefixFilters['prefix-supported-value'] = 'Prefixed Values';
1041         prefixFilters['prefix-only-value'] = 'Prefixed-only Values';
1042
1043         for (var key in prefixFilters) {
1044             var status = prefixFilters[key];
1045             var entry = document.createElement("li");
1046             var label = document.createElement("label");
1047             var input = document.createElement("input");
1048             input.setAttribute('type','checkbox');
1049             input.setAttribute('value', key);
1050             input.addEventListener('change', function() { updateSearch(properties); });
1051             label.appendChild(input);
1052             label.className = status.toLocaleLowerCase().replace(/ /g, '-');
1053             label.appendChild(document.createTextNode(" " + status));
1054             entry.appendChild(label);
1055             prefixContainer.appendChild(entry);
1056         }
1057
1058         filtersForm.addEventListener('click', function (e) {
1059             if ( filtersForm.className.indexOf('opened') !== -1 ) {
1060                 filtersForm.className = filtersForm.className.replace(' opened','');
1061             } else filtersForm.className += ' opened';
1062         });
1063
1064         inputField.addEventListener('input', function() { updateSearch(properties); });
1065
1066         var inputs = [].slice.call(filtersForm.getElementsByTagName('input'));
1067         inputs.forEach(function (input,i) {
1068             input.addEventListener('click', function (e) {
1069                 e.stopPropagation();
1070             });
1071         });
1072
1073         renderSpecifications(categories, properties);
1074     }
1075
1076     function getValuesOfCheckedItems(items)
1077     {
1078         var checkedValues = [];
1079         items.forEach(function(item,i) {
1080             if (item.checked)
1081                 checkedValues.push(item.value);
1082         });
1083         
1084         return checkedValues;
1085     }
1086
1087     function selectedSpecifications()
1088     {
1089         if (!document.getElementById('by-spec-checkbox').checked)
1090             return [];
1091
1092         var specificationsList = document.getElementById('specifications');
1093         return getValuesOfCheckedItems([].slice.call(specificationsList.getElementsByTagName('input')));
1094     }
1095     
1096     function updateSearch(properties)
1097     {
1098         var inputField = document.getElementById('search');
1099         var statusContainer = document.getElementById('status-filters');
1100
1101         var searchTerm = inputField.value.trim().toLowerCase();
1102         var activeStatusFilters = getValuesOfCheckedItems([].slice.call(statusContainer.querySelectorAll('.status-checkbox')));
1103
1104         var prefixContainer = document.getElementById('prefix-filters');
1105         var activePrefixFilters = getValuesOfCheckedItems([].slice.call(prefixContainer.getElementsByTagName('input')));
1106
1107         var numVisible = searchProperties(properties, searchTerm, selectedSpecifications(), activeStatusFilters, activePrefixFilters);
1108         document.getElementById('property-pluralize').textContent = numVisible == 1 ? 'property' : 'properties';
1109         document.getElementById('property-count').textContent = numVisible;
1110         
1111         updateSpecsState();
1112     }
1113     
1114     function updateSpecsState()
1115     {
1116         var specsEnabled = document.getElementById('by-spec-checkbox').checked;
1117         var specificationsList = document.getElementById('specifications');
1118         
1119         var radiobuttons = [].slice.call(specificationsList.getElementsByTagName('input'));
1120         radiobuttons.forEach(function(radiobutton,i) {
1121             radiobutton.disabled = !specsEnabled;
1122         });
1123     }
1124
1125     function valueOrAliasIsPrefixed(valueObj)
1126     {
1127         if (prefixRegexp.exec(valueObj.value))
1128             return true;
1129         
1130         if ('alias' in valueObj) {
1131             for (var alias of valueObj.aliases) {
1132                 if (prefixRegexp.exec(alias))
1133                     return true;
1134             }
1135         }
1136         
1137         return false;
1138     }
1139
1140     function filterValues(propertyObject, searchTerm, categories, statusFilters)
1141     {
1142         for (var valueObj of propertyObject.values) {
1143             if (!valueObj.el)
1144                 continue;
1145
1146             var visible = false;
1147             visible = valueObj.value.toLowerCase().indexOf(searchTerm) !== -1;
1148
1149             if (!visible) {
1150                 for (var currValueObj of propertyObject.values) {
1151                     if (valueOrAliasIsPrefixed(currValueObj)) {
1152                         visible = true;
1153                         break;
1154                     }
1155                 }
1156             }
1157
1158             if (!visible) {
1159                 for (var currValueObj of propertyObject.values) {
1160                     if (prefixRegexp.exec(currValueObj.value)) {
1161                         visible = true;
1162                         break;
1163                     }
1164                 }
1165             }
1166
1167             if (visible)
1168                 valueObj.el.classList.remove('hidden');
1169             else
1170                 valueObj.el.classList.add('hidden');
1171         }
1172     }
1173
1174     function searchProperties(properties, searchTerm, categories, statusFilters, prefixFilters)
1175     {
1176         var visibleCount = 0;
1177         properties.forEach(function(propertyObject) {
1178             var matchesStatusSearch = isStatusFiltered(propertyObject, statusFilters);
1179             var matchesPrefixSearch = isPrefixFiltered(propertyObject, prefixFilters);
1180             
1181             var visible = propertyIsSearchMatch(propertyObject, searchTerm) && isCategoryMatch(propertyObject, categories) && matchesStatusSearch && matchesPrefixSearch;
1182             if (visible && !propertyObject.visible)
1183                 propertyObject.el.className = 'property';
1184             else if (!visible && propertyObject.visible)
1185                 propertyObject.el.className = 'property is-hidden';
1186             
1187             if (visible) {
1188                 filterValues(propertyObject, searchTerm);
1189                 ++visibleCount;
1190             }
1191
1192             propertyObject.visible = visible;
1193         });
1194         
1195         return visibleCount;
1196     }
1197
1198     function propertyIsSearchMatch(propertyObject, searchTerm)
1199     {
1200         if (propertyObject.name.toLowerCase().indexOf(searchTerm) !== -1)
1201             return true;
1202
1203         if ('keywords' in propertyObject) {
1204             for (var keyword of propertyObject.keywords) {
1205                 if (keyword.toLowerCase().indexOf(searchTerm) !== -1)
1206                     return true;
1207             }
1208         }
1209         
1210         for (var valueObj of propertyObject.values) {
1211             if (valueObj.value.toLowerCase().indexOf(searchTerm) !== -1)
1212                 return true;
1213         }
1214
1215         return false;
1216     }
1217
1218     function getSpecificationCategory(propertyObject)
1219     {
1220         if ('specification' in propertyObject) {
1221             var specification = propertyObject.specification;
1222             if ('category' in specification) {
1223                 return specification.category;
1224             }
1225         }
1226         return undefined;
1227     }
1228
1229     function isCategoryMatch(propertyObject, categories)
1230     {
1231         if (!categories.length)
1232             return true;
1233
1234         var category;
1235         if (category = getSpecificationCategory(propertyObject)) {
1236             if (categories.indexOf(category) !== -1) {
1237                 return true;
1238             }
1239         }
1240         return false;
1241     }
1242     
1243     function propertyOrAliasIsPrefixed(propertyObject)
1244     {
1245         if (prefixRegexp.exec(propertyObject.name))
1246             return true;
1247         
1248         for (var alias of propertyNameAliases(propertyObject)) {
1249             if (prefixRegexp.exec(alias))
1250                 return true;
1251         }
1252         
1253         return false;
1254     }
1255
1256     function isStatusFiltered(propertyObject, activeFilters)
1257     {
1258         if (activeFilters.length == 0)
1259             return true;
1260         if (propertyObject.status === undefined)
1261             return false;
1262         if (activeFilters.indexOf(propertyObject.status.status.toLowerCase()) !== -1)
1263             return true;
1264         
1265         return false;
1266     }
1267
1268     function isPrefixFiltered(propertyObject, activeFilters)
1269     {
1270         if (activeFilters.length == 0)
1271             return true;
1272
1273         if (activeFilters.indexOf('prefix-only-property') !== -1)
1274             return prefixRegexp.exec(propertyObject.name);
1275
1276         if (activeFilters.indexOf('prefix-supported-property') !== -1)
1277             return propertyOrAliasIsPrefixed(propertyObject);
1278
1279         if (activeFilters.indexOf('prefix-supported-value') !== -1) {
1280             for (var valueObj of propertyObject.values) {
1281                 if (valueOrAliasIsPrefixed(valueObj))
1282                     return true;
1283             }
1284         }
1285
1286         if (activeFilters.indexOf('prefix-only-value') !== -1) {
1287             for (var valueObj of propertyObject.values) {
1288                 if (prefixRegexp.exec(valueObj.value))
1289                     return true;
1290             }
1291         }
1292         
1293         return false;
1294     }
1295
1296     function findValueByName(values, name)
1297     {
1298         return values.find(function(element) {
1299             return element.value === name;
1300         })
1301     }
1302
1303     function mergeProperties(unprefixedPropertyObj, prefixedPropertyObj)
1304     {
1305         (unprefixedPropertyObj['codegen-properties'].aliases = unprefixedPropertyObj['codegen-properties'].aliases || []).push(prefixedPropertyObj.name);
1306         
1307         for (var valueObj of prefixedPropertyObj.values) {
1308             if (!findValueByName(unprefixedPropertyObj.values, valueObj.value))
1309                 prefixedPropertyObj.values.push(valueObj);
1310         }
1311
1312         return unprefixedPropertyObj;
1313     }
1314     
1315     // Sometimes we have separate entries for -webkit-foo and foo.
1316     function collapsePrefixedProperties(properties)
1317     {
1318         function findPropertyByName(properties, name)
1319         {
1320             return properties.find(function(element) {
1321                 return element.name === name;
1322             })
1323         }
1324
1325         var remainingProperties = [];
1326         var prefixMap = {};
1327
1328         for (var propertyObj of properties) {
1329             var propertyName = propertyObj.name;
1330
1331             var result = prefixRegexp.exec(propertyName);
1332             if (result) {
1333                 var unprefixed = result[2];
1334                 var unprefixedProperty = findPropertyByName(properties, unprefixed);
1335                 if (unprefixedProperty) {
1336                     mergeProperties(unprefixedProperty, propertyObj);
1337                     continue;
1338                 }
1339             }
1340
1341             remainingProperties.push(propertyObj);
1342         }
1343         
1344         return remainingProperties;
1345     }
1346
1347     function canonicalizeValues(propertyObject)
1348     {
1349         var valueObjects = [];
1350         // Convert all values to objects.
1351         if ('values' in propertyObject) {
1352             for (var value of propertyObject.values) {
1353                 if (typeof value === 'object')
1354                     valueObjects.push(value);
1355                 else
1356                     valueObjects.push({ 'value' : value });
1357             }
1358         }
1359         propertyObject.values = valueObjects;
1360     }
1361     
1362     function canonicalizeStatus(propertyObject, categories)
1363     {
1364         // Inherit "status" from the cateogry if not explicitly specified.
1365         if (!('status' in propertyObject)) {
1366             var category = getSpecificationCategory(propertyObject)
1367             if (category) {
1368                 var categoryObject = categories[category];
1369                 if (categoryObject) {
1370                     if ('status' in categoryObject) {
1371                         propertyObject.status = {
1372                             'status' : categoryObject.status
1373                         };
1374                     }
1375                 }
1376             }
1377         } else {
1378             // Convert all values to objects.
1379             if (typeof propertyObject.status === 'string')
1380                 propertyObject.status = { 'status': propertyObject.status };
1381         }
1382
1383         if (!('status' in propertyObject)) {
1384             propertyObject.status = {
1385                 'status' : 'done',
1386                 'enabled-by-default' : true
1387             };
1388         } else if (!('status' in propertyObject.status))
1389             propertyObject.status.status = 'done';
1390     }
1391     
1392     function displayFeatures(results)
1393     {
1394         var mainContent = document.getElementById("property-list");
1395         var successSubtree = document.importNode(document.getElementById("success-template").content, true);
1396         mainContent.appendChild(successSubtree);
1397         
1398         var properties = results[0]['properties'];
1399         var everythingToShow = [];
1400         
1401         var categories = results[0]['categories'];
1402
1403         for (var property in properties) {
1404             var propertyObject = properties[property];
1405             propertyObject.name = property;
1406
1407             canonicalizeValues(propertyObject);
1408             canonicalizeStatus(propertyObject, categories);
1409
1410             everythingToShow.push(propertyObject);
1411         }
1412         
1413         everythingToShow = collapsePrefixedProperties(everythingToShow);
1414         sortAlphabetically(everythingToShow);
1415         
1416         renderProperties(categories, everythingToShow);
1417
1418         initSearch(everythingToShow, categories);
1419         
1420         updateSearch(everythingToShow);
1421     }
1422
1423     function displayError(error)
1424     {
1425         window.console.log('displayError', error)
1426         var mainContent = document.getElementById("property-list");
1427         var successSubtree = document.importNode(document.getElementById("error-template").content, true);
1428
1429         var errorMessage = "Unable to load " + error.url;
1430
1431         if (error.request.status !== 200) {
1432             errorMessage += ", status: " + error.request.status + " - " + error.request.statusText;
1433         } else if (!error.response) {
1434             errorMessage += ", the JSON file cannot be processed.";
1435         }
1436
1437         successSubtree.querySelector("#error-message").textContent = errorMessage;
1438
1439         mainContent.appendChild(successSubtree);
1440     }
1441
1442     Promise.all([loadCSSProperties]).then(displayFeatures).catch(displayError);
1443 }
1444
1445 document.addEventListener("DOMContentLoaded", initializeStatusPage);
1446 </script>
1447
1448 <?php get_footer(); ?>