Add JavaScript and CSS code for the calendar picker implementation
authortkent@chromium.org <tkent@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 5 Apr 2012 07:30:51 +0000 (07:30 +0000)
committertkent@chromium.org <tkent@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 5 Apr 2012 07:30:51 +0000 (07:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=83011

Reviewed by Hajime Morita.

.:

* ManualTests/forms/calendar-picker.html: Added.

Source/WebCore:

Add calendarPicker.js and calendarPicker.css, and add a build rule to
generate a C++ file. This change doesn't make any behavior change
because the code is wrapped by ENABLE(CALENDAR_PICKER).

Tests: ManualTests/forms/calendar-picker.html

* Resources/calendarPicker.css: Added.
* Resources/calendarPicker.js: Added.
* WebCore.gyp/WebCore.gyp: Generate CalendarPicker.{cpp,h} from calendarPicker.{css,js}.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@113298 268f45cc-cd09-0410-ab3c-d52691b4dbfc

ChangeLog
ManualTests/forms/calendar-picker.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Resources/calendarPicker.css [new file with mode: 0644]
Source/WebCore/Resources/calendarPicker.js [new file with mode: 0644]
Source/WebCore/WebCore.gyp/WebCore.gyp

index 324ee2f..90b4f62 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2012-04-04  Kent Tamura  <tkent@chromium.org>
+
+        Add JavaScript and CSS code for the calendar picker implementation
+        https://bugs.webkit.org/show_bug.cgi?id=83011
+
+        Reviewed by Hajime Morita.
+
+        * ManualTests/forms/calendar-picker.html: Added.
+
 2012-04-04  Hayato Ito  <hayato@chromium.org>
 
         Remove ReifiedTreeTraversal.
diff --git a/ManualTests/forms/calendar-picker.html b/ManualTests/forms/calendar-picker.html
new file mode 100644 (file)
index 0000000..1425e88
--- /dev/null
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>Calendar Picker test</title>
+<style>
+body {
+    background-color: #eeffff;
+}
+iframe {
+    z-index: 2147483647;
+    width: 290px;
+    height: 256px;
+    border: 0;
+    overflow: hidden;
+}
+</style>
+</head>
+<body>
+
+<p>This is a testbed for a calendar picker.</p>
+
+<div><input type="text" id="date"></div>
+<iframe></iframe>
+
+<ol id="console" style="font-family:monospace;">
+</ol>
+
+<script>
+var frame = document.getElementsByTagName('iframe')[0];
+var doc = frame.contentDocument;
+doc.documentElement.innerHTML = '<head></head><body><div id=main>Loading...</div></body>';
+var link = doc.createElement('link');
+link.rel = 'stylesheet';
+link.href = '../../Source/WebCore/Resources/calendarPicker.css?' + (new Date()).getTime();
+doc.head.appendChild(link);
+var script = doc.createElement('script');
+script.src = '../../Source/WebCore/Resources/calendarPicker.js?' + (new Date()).getTime();
+doc.body.appendChild(script);
+
+var englishArguments = {
+    locale: 'en-US',
+    monthLabels : ['January', 'February', 'March', 'April', 'May', 'June',
+    'July', 'August', 'September', 'October', 'November', 'December'],
+    dayLabels : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+    todayLabel : 'Today',
+    clearLabel : 'Clear',
+    cancelLabel : 'Cancel',
+    weekStartDay : 0,
+    step : 1,
+    max : '2099-12-31',
+};
+var japaneseArguments = {
+    locale: 'ja-JP',
+    monthLabels : ['1月', '2月', '3月', '4月', '5月', '6月',
+    '7月', '8月', '9月', '10月', '11月', '12月'],
+    dayLabels : ['日', '月', '火', '水', '木', '金', '土'],
+    todayLabel : '今日',
+    clearLabel : 'クリア',
+    cancelLabel : '取り消し',
+    weekStartDay : 0,
+    step : 1,
+    max : '2099-03-15',
+};
+setTimeout(function() {
+    frame.contentWindow.postMessage(JSON.stringify(englishArguments), "*");
+    frame.contentWindow.setValueAndClosePopup = function(numValue, stringValue) {
+        window.log('number=' + numValue + ', string="' + stringValue + '"');
+        if (numValue == 0)
+            window.document.getElementById('date').value = stringValue;
+    };
+}, 100);
+
+
+function log(str) {
+    var entry = document.createElement('li');
+    entry.innerText = str;
+    document.getElementById('console').appendChild(entry);
+}
+</script>
+</body>
+</html>
index 4c8ec9c..4e219e2 100644 (file)
@@ -1,3 +1,20 @@
+2012-04-04  Kent Tamura  <tkent@chromium.org>
+
+        Add JavaScript and CSS code for the calendar picker implementation
+        https://bugs.webkit.org/show_bug.cgi?id=83011
+
+        Reviewed by Hajime Morita.
+
+        Add calendarPicker.js and calendarPicker.css, and add a build rule to
+        generate a C++ file. This change doesn't make any behavior change
+        because the code is wrapped by ENABLE(CALENDAR_PICKER).
+
+        Tests: ManualTests/forms/calendar-picker.html
+
+        * Resources/calendarPicker.css: Added.
+        * Resources/calendarPicker.js: Added.
+        * WebCore.gyp/WebCore.gyp: Generate CalendarPicker.{cpp,h} from calendarPicker.{css,js}.
+
 2012-04-05  Kinuko Yasuda  <kinuko@chromium.org>
 
         Expose DataTransferItem.getAsEntry() to allow users access dropped files as FileEntry
diff --git a/Source/WebCore/Resources/calendarPicker.css b/Source/WebCore/Resources/calendarPicker.css
new file mode 100644 (file)
index 0000000..72b5943
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+body {
+    -webkit-user-select: none;
+    background-color: purple;
+    font: -webkit-small-control;
+    margin: 0;
+    overflow: hidden;
+}
+
+.no-focus-ring :focus {
+    outline: none;
+}
+
+#main {
+    background-color: white;
+    border: solid 1px #8899aa;
+    box-shadow: inset 2px 2px 2px white,
+        inset -2px -2px 1px rgba(0,0,0,0.1);
+    padding: 6px;
+}
+
+.year-month-area {
+}
+
+.year-month-upper {
+    display: -webkit-box;
+}
+
+.year-selector {
+    text-align: right;
+    width: 6em;
+}
+
+.month-selector-box {
+    display: inline-box;
+    text-align: center;
+    -webkit-box-flex: 2;
+}
+
+.month-selector {
+    background-color: white;
+    cursor: pointer;
+    display: inline-block;
+    padding: 3px 6px 3px 6px;
+}
+.month-selector:after {
+    content: " ▼";
+    font-size: smaller;
+}
+
+.month-selector-popup {
+    display: none;
+    outline: none;
+    z-index: 2;
+}
+
+.month-selector-wall {
+    display: none;
+    height: 100%;
+    left: 0px;
+    position: absolute;
+    top: 0px;
+    width: 100%;
+}
+
+/* FIXME: We'd like to use the standard button appearance for non-Mac. */
+.year-month-button {
+    -webkit-appearance: none;
+    background: -webkit-gradient(linear, left top, left bottom, from(#eee), color-stop(0.4, #fff), color-stop(0.6, #dddddd), to(#eee));
+    border: solid 1px #444;
+    border-radius: 3px;
+    box-shadow: inset 1px 1px 1px rgba(255,255,255,0.4), inset -1px -1px 1px rgba(255,255,255,0.4);
+    padding-left: 3px;
+    padding-right: 3px;
+}
+
+.year-month-button:hover {
+    background: -webkit-gradient(linear, left top, left bottom, from(#e0f0ff), color-stop(0.4, #e0f0ff), color-stop(0.6, #b0e0ff), to(#b0e0ff));
+    border: solid 1px #448;
+}
+
+.year-month-button:active {
+    border: solid 1px black;
+    box-shadow: inset 1px 1px 1px rgba(0,0,0,0.3), inset -1px -1px 1px rgba(0,0,0,0.3);
+}
+
+.year-month-button:disabled {
+    color: #888;
+    background: -webkit-gradient(linear, left top, left bottom, from(#eee), color-stop(0.4, #fff), color-stop(0.6, #dddddd), to(#eee));
+    border: solid 1px #ccc;
+    box-shadow: inset 1px 1px 1px rgba(255,255,255,0.4), inset -1px -1px 1px rgba(255,255,255,0.4);
+}
+
+.year-month-button-left {
+    display: inline-block;
+}
+
+.year-month-button-right {
+    display: inline-block;
+}
+
+.days-area-container {
+    background-color: white;
+    border: solid 1px black;
+    margin-bottom: 4px;
+    margin-top: 4px;
+    overflow: hidden;
+}
+
+.days-area { /* <table> */
+    background-color: white;
+    border-collapse: separate;
+    border-spacing: 0px;
+    font: -webkit-small-control;
+    table-layout: fixed;
+    width: 100%;
+}
+
+.day-label { /* <th> */
+    -webkit-box-sizing: border-box;
+    background-color: #e3e9ff;
+    border-left-color: #f0f4ff;
+    border-top-color: #f0f4ff;
+    border: solid 1px #d0d4f0;
+    color: #20c;
+    font-weight: normal;
+    text-align: center;
+}
+
+.day { /* <td> */
+    padding: 1px;
+    text-align: center;
+    background-color: white;
+    border: 1px solid white;
+    -webkit-box-sizing: border-box;
+}
+
+.available {
+    cursor: default;
+    font-weight: 700;
+    background-color: white;
+    border: 1px solid white;
+    border-radius: 5px;
+    -webkit-transition: all 0.2s ease;
+}
+
+.not-this-month {
+    color: graytext;
+    font-weight: 400;
+    text-shadow: none;
+}
+
+.available.day-selected {
+    background-color: highlight;
+    color: highlighttext;
+    text-shadow: none;
+}
+
+.unavailable {
+    cursor: default;
+    color: graytext;
+    background-color: #eee;
+    border: 1px solid #eee;
+    -webkit-transition: border-color 0.2s ease;
+}
+
+.unavailable.day-selected {
+    border: 1px solid highlight;
+}
+
+.cancel-button {
+    float: right;
+}
diff --git a/Source/WebCore/Resources/calendarPicker.js b/Source/WebCore/Resources/calendarPicker.js
new file mode 100644 (file)
index 0000000..c300b8c
--- /dev/null
@@ -0,0 +1,1074 @@
+"use strict";
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// FIXME:
+//  - RTL
+//  - Touch event
+
+/**
+ * CSS class names.
+ *
+ * @enum {string}
+ */
+var ClassNames = {
+    Available: "available",
+    CancelButton: "cancel-button",
+    ClearButton: "clear-button",
+    Day: "day",
+    DayLabel: "day-label",
+    DayLabelContainer: "day-label-container",
+    DaysArea: "days-area",
+    DaysAreaContainer: "days-area-container",
+    MonthSelector: "month-selector",
+    MonthSelectorBox: "month-selector-box",
+    MonthSelectorPopup: "month-selector-popup",
+    MonthSelectorWall: "month-selector-wall",
+    NoFocusRing: "no-focus-ring",
+    NotThisMonth: "not-this-month",
+    Selected: "day-selected",
+    TodayButton: "today-button",
+    TodayClearArea: "today-clear-area",
+    Unavailable: "unavailable",
+    WeekContainer: "week-container",
+    YearMonthArea: "year-month-area",
+    YearMonthButton: "year-month-button",
+    YearMonthButtonLeft: "year-month-button-left",
+    YearMonthButtonRight: "year-month-button-right",
+    YearMonthUpper: "year-month-upper"
+};
+
+/**
+ * @type {Object}
+ */
+var global = {
+    argumentsReceived: false,
+    hadKeyEvent: false,
+    params: null
+};
+
+// ----------------------------------------------------------------
+// Utility functions
+
+/**
+ * @param {!string} id
+ */
+function $(id) {
+    return document.getElementById(id);
+}
+
+function bind(func, context) {
+    return function() {
+        return func.apply(context, arguments);
+    };
+}
+
+/**
+ * @param {!string} tagName
+ * @param {string=} opt_class
+ * @param {string=} opt_text
+ * @return {!Element}
+ */
+function createElement(tagName, opt_class, opt_text) {
+    var element = document.createElement(tagName);
+    if (opt_class)
+        element.setAttribute("class", opt_class);
+    if (opt_text)
+        element.appendChild(document.createTextNode(opt_text));
+    return element;
+}
+
+/**
+ * @return {!string} lowercase locale name. e.g. "en-us"
+ */
+function getLocale() {
+    return (global.params.locale || "en-us").toLowerCase();
+}
+
+/**
+ * @return {!string} lowercase language code. e.g. "en"
+ */
+function getLanguage() {
+    var locale = getLocale();
+    var result = locale.match(/^([a-z]+)/);
+    if (!result)
+        return "en";
+    return result[1];
+}
+
+/**
+ * @param {!number} year
+ * @param {!number} month
+ * @return {!string}
+ */
+function formatJapaneseImperialEra(year, month) {
+    if (year > 1989)
+        return "(平成" + (year - 1988) + "年)";
+    if (year == 1989)
+        return "(平成元年)";
+    if (year >= 1927)
+        return "(昭和" + (year - 1925) + "年)";
+    if (year > 1912)
+        return "(大正" + (year - 1911) + "年)";
+    if (year == 1912 && month >= 7)
+        return "(大正元年)";
+    if (year > 1868)
+        return "(明治" + (year - 1867) + "年)";
+    if (year == 1868)
+        return "(明治元年)";
+    return "";
+}
+
+/**
+ * @param {!number} year
+ * @param {!number} month
+ * @return {!string}
+ */
+function formatYearMonth(year, month) {
+    // FIXME: Need localized number?
+    var yearString = String(year);
+    var monthString = global.params.monthLabels[month];
+    switch (getLanguage()) {
+    case "eu":
+    case "fil":
+    case "lt":
+    case "ml":
+    case "mt":
+    case "tl":
+    case "ur":
+        return yearString + " " + monthString;
+    case "hu":
+        return yearString + ". " + monthString;
+    case "ja":
+        return yearString + "年" + formatJapaneseImperialEra(year, month) + " " + monthString;
+    case "zh":
+        return yearString + "年" + monthString;
+    case "ko":
+        return yearString + "년 " + monthString;
+    case "lv":
+        return yearString + ". g. " + monthString;
+    case "pt":
+        return monthString + " de " + yearString;
+    case "sr":
+        return monthString + ". " + yearString;
+    default:
+        return monthString + " " + yearString;
+    }
+}
+
+/**
+ * @param {string=} opt_current
+ * @return {!Date}
+ */
+function parseDateString(opt_current) {
+    if (opt_current) {
+        var result = opt_current.match(/(\d+)-(\d+)-(\d+)/);
+        if (result)
+            return new Date(Date.UTC(Number(result[1]), Number(result[2]) - 1, Number(result[3])));
+    }
+    var now = new Date();
+    // Create UTC date with same numbers as local date.
+    return new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
+}
+
+/**
+ * @param {!number} year
+ * @param {!number} month
+ * @param {!number} day
+ * @return {!string}
+ */
+function serializeDate(year, month, day) {
+    var yearString = String(year);
+    if (yearString.length < 4)
+        yearString = ("000" + yearString).substr(-4, 4);
+    return yearString + "-" + ("0" + (month + 1)).substr(-2, 2) + "-" + ("0" + day).substr(-2, 2);
+}
+
+// ----------------------------------------------------------------
+// Initialization
+
+/**
+ * @param {Event} event
+ */
+function handleMessage(event) {
+    if (global.argumentsReceived)
+        return;
+    global.argumentsReceived = true;
+    initialize(JSON.parse(event.data));
+}
+
+function handleArgumentsTimeout() {
+    if (global.argumentsReceived)
+        return;
+    var args = {
+        monthLabels : ["m1", "m2", "m3", "m4", "m5", "m6",
+                       "m7", "m8", "m9", "m10", "m11", "m12"],
+        dayLabels : ["d1", "d2", "d3", "d4", "d5", "d6", "d7"],
+        todayLabel : "Today",
+        clearLabel : "Clear",
+        cancelLabel : "Cancel",
+        currentValue : "",
+        weekStartDay : 0,
+        step : 1
+    };
+    initialize(args);
+}
+
+/**
+ * @param {!Object} args
+ * @return {?string} An error message, or null if the argument has no errors.
+ */
+function validateArguments(args) {
+    if (!args.monthLabels)
+        return "No monthLabels.";
+    if (args.monthLabels.length != 12)
+        return "monthLabels is not an array with 12 elements.";
+    if (!args.dayLabels)
+        return "No dayLabels.";
+    if (args.dayLabels.length != 7)
+        return "dayLabels is not an array with 7 elements.";
+    if (!args.clearLabel)
+        return "No clearLabel.";
+    if (!args.todayLabel)
+        return "No todayLabel.";
+    if (args.weekStartDay) {
+        if (args.weekStartDay < 0 || args.weekStartDay > 6)
+            return "Invalid weekStartDay: " + args.weekStartDay;
+    }
+    return null;
+}
+
+/**
+ * @param {!Object} args
+ */
+function initialize(args) {
+    var main = $("main");
+    main.classList.add(ClassNames.NoFocusRing);
+
+    var errorString = validateArguments(args);
+    if (errorString)
+        main.textContent = "Internal error: " + errorString;
+    else {
+        global.params = args;
+        checkLimits();
+        layout();
+
+        var initialDate = parseDateString(args.currentValue);
+        if (initialDate < global.minimumDate)
+            initialDate = global.minimumDate;
+        else if (initialDate > global.maximumDate)
+            initialDate = global.maximumDate;
+        global.daysTable.selectDate(initialDate);
+
+        setTimeout(function() {
+            if (window.frameElement)
+                window.frameElement.style.height = main.offsetHeight + "px";
+            else
+                window.resizeTo(window.outerWidth, main.offsetTop + main.offsetHeight);
+        }, 0);
+    }
+}
+
+function checkLimits() {
+    // Hard limits of type=date. See WebCore/platform/DateComponents.h.
+    global.minimumDate = new Date(-62135596800000.0);
+    global.maximumDate = new Date(8640000000000000.0);
+    // See WebCore/html/DateInputType.cpp.
+    global.step = 86400000;
+
+    if (global.params.min) {
+        // We assume params.min is a valid date.
+        global.minimumDate = parseDateString(global.params.min);
+    }
+    if (global.params.max) {
+        // We assume params.max is a valid date.
+        global.maximumDate = parseDateString(global.params.max);
+    }
+    if (global.params.step)
+        global.step *= global.params.step;
+}
+
+function layout() {
+    var main = $("main");
+    var params = global.params;
+    main.removeChild(main.firstChild);
+    document.body.addEventListener("keydown", handleGlobalKey, false);
+
+    global.yearMonthController = new YearMonthController();
+    global.yearMonthController.attachTo(main);
+    global.daysTable = new DaysTable();
+    global.daysTable.attachTo(main);
+    layoutButtons(main);
+}
+
+/**
+ * @param {Element} main
+ */
+function layoutButtons(main) {
+    var container = createElement("div", ClassNames.TodayClearArea);
+    global.today = createElement("input", ClassNames.TodayButton);
+    global.today.type = "button";
+    global.today.value = global.params.todayLabel;
+    global.today.addEventListener("click", handleToday, false);
+    container.appendChild(global.today);
+    global.clear = null;
+    if (!global.params.required) {
+        global.clear = createElement("input", ClassNames.ClearButton);
+        global.clear.type = "button";
+        global.clear.value = global.params.clearLabel;
+        global.clear.addEventListener("click", handleClear, false);
+        container.appendChild(global.clear);
+    }
+    main.appendChild(container);
+
+    global.lastFocusableControl = global.clear || global.today;
+}
+
+// ----------------------------------------------------------------
+
+/**
+ * @constructor
+ */
+function YearMonthController() {
+    /**
+     * @type {!number}
+     */
+    this._currentYear = -1;
+    /**
+     * @type {!number}
+     */
+    this._currentMonth = -1;
+}
+
+/**
+ * @param {!Element} main
+ */
+YearMonthController.prototype.attachTo = function(main) {
+    var outerContainer = createElement("div", ClassNames.YearMonthArea);
+
+    var innerContainer = createElement("div", ClassNames.YearMonthUpper);
+    outerContainer.appendChild(innerContainer);
+
+    this._attachLeftButtonsTo(innerContainer);
+
+    var box = createElement("div", ClassNames.MonthSelectorBox);
+    innerContainer.appendChild(box);
+    // We can't use <select> popup in PagePopup.
+    // FIXME: The popup-menu emulation by a listbox is not great.
+    this._monthPopup = createElement("select", ClassNames.MonthSelectorPopup);
+    this._monthPopup.addEventListener("click", bind(this._handleYearMonthChange, this), false);
+    this._monthPopup.addEventListener("keydown", bind(this._handleMonthPopupKey, this), false);
+    box.appendChild(this._monthPopup);
+    this._month = createElement("div", ClassNames.MonthSelector);
+    this._month.addEventListener("click", bind(this._showPopup, this), false);
+    box.appendChild(this._month);
+
+    this._attachRightButtonsTo(innerContainer);
+    main.appendChild(outerContainer);
+
+    this._wall = createElement("div", ClassNames.MonthSelectorWall);
+    this._wall.addEventListener("click", bind(this._closePopup, this), false);
+    main.appendChild(this._wall);
+
+    global.firstFocusableControl = this._left2; // FIXME: Shoud it be this.month?
+};
+
+YearMonthController.addTenYearsButtons = false;
+
+/**
+ * @param {!Element} parent
+ */
+YearMonthController.prototype._attachLeftButtonsTo = function(parent) {
+    var container = createElement("div", ClassNames.YearMonthButtonLeft);
+    parent.appendChild(container);
+
+    if (YearMonthController.addTenYearsButtons) {
+        this._left3 = createElement("input", ClassNames.YearMonthButton);
+        this._left3.type = "button";
+        this._left3.value = "<<<";
+        this._left3.addEventListener("click", bind(this._handleButtonClick, this), false);
+        container.appendChild(this._left3);
+    }
+
+    this._left2 = createElement("input", ClassNames.YearMonthButton);
+    this._left2.type = "button";
+    this._left2.value = "<<";
+    this._left2.addEventListener("click", bind(this._handleButtonClick, this), false);
+    container.appendChild(this._left2);
+
+    this._left1 = createElement("input", ClassNames.YearMonthButton);
+    this._left1.type = "button";
+    this._left1.value = "<";
+    this._left1.addEventListener("click", bind(this._handleButtonClick, this), false);
+    container.appendChild(this._left1);
+};
+
+/**
+ * @param {!Element} parent
+ */
+YearMonthController.prototype._attachRightButtonsTo = function(parent) {
+    var container = createElement("div", ClassNames.YearMonthButtonRight);
+    parent.appendChild(container);
+    this._right1 = createElement("input", ClassNames.YearMonthButton);
+    this._right1.type = "button";
+    this._right1.value = ">";
+    this._right1.addEventListener("click", bind(this._handleButtonClick, this), false);
+    container.appendChild(this._right1);
+
+    this._right2 = createElement("input", ClassNames.YearMonthButton);
+    this._right2.type = "button";
+    this._right2.value = ">>";
+    this._right2.addEventListener("click", bind(this._handleButtonClick, this), false);
+    container.appendChild(this._right2);
+
+    if (YearMonthController.addTenYearsButtons) {
+        this._right3 = createElement("input", ClassNames.YearMonthButton);
+        this._right3.type = "button";
+        this._right3.value = ">>>";
+        this._right3.addEventListener("click", bind(this._handleButtonClick, this), false);
+        container.appendChild(this._right3);
+    }
+};
+
+/**
+ * @return {!number}
+ */
+YearMonthController.prototype.year = function() {
+    return this._currentYear;
+};
+
+/**
+ * @return {!number}
+ */
+YearMonthController.prototype.month = function() {
+    return this._currentMonth;
+};
+
+/**
+ * @param {!number} year
+ * @param {!number} month
+ */
+YearMonthController.prototype.setYearMonth = function(year, month) {
+    this._currentYear = year;
+    this._currentMonth = month;
+    this._redraw();
+};
+
+YearMonthController.prototype._redraw = function() {
+    var min = global.minimumDate.getUTCFullYear() * 12 + global.minimumDate.getUTCMonth();
+    var max = global.maximumDate.getUTCFullYear() * 12 + global.maximumDate.getUTCMonth();
+    var current = this._currentYear * 12 + this._currentMonth;
+    if (this._left3)
+        this._left3.disabled = current - 13 < min;
+    this._left2.disabled = current - 2 < min;
+    this._left1.disabled = current - 1 < min;
+    this._right1.disabled = current + 1 > max;
+    this._right2.disabled = current + 2 > max;
+    if (this._right3)
+        this._right3.disabled = current + 13 > max;
+    this._month.innerText = formatYearMonth(this._currentYear, this._currentMonth);
+    while (this._monthPopup.hasChildNodes())
+        this._monthPopup.removeChild(this._monthPopup.firstChild);
+    for (var m = current - 6; m <= current + 6; m++) {
+        if (m < min || m > max)
+            continue;
+        var option = createElement("option", undefined, formatYearMonth(Math.floor(m / 12), m % 12));
+        option.value = String(Math.floor(m / 12)) + "-" + String(m % 12);
+        this._monthPopup.appendChild(option);
+        if (m == current)
+            option.selected = true;
+    }
+};
+
+YearMonthController.prototype._showPopup = function() {
+    this._monthPopup.size = Math.max(4, Math.min(10, this._monthPopup.length));
+    this._monthPopup.style.display = "block";
+    this._monthPopup.style.position = "absolute";
+    this._monthPopup.style.zIndex = "1000"; // Larger than the days area.
+    this._monthPopup.style.left = this._month.offsetLeft + "px";
+    this._monthPopup.style.top = this._month.offsetTop + this._month.offsetHeight + "px";
+    this._monthPopup.focus();
+
+    this._wall.style.display = "block";
+    this._wall.style.zIndex = "999"; // This should be smaller than the z-index of monthPopup.
+};
+
+YearMonthController.prototype._closePopup = function() {
+    this._monthPopup.style.display = "none";
+    this._wall.style.display = "none";
+};
+
+/**
+ * @param {Event} event
+ */
+YearMonthController.prototype._handleMonthPopupKey = function(event)
+{
+    var key = event.keyIdentifier;
+    if (key == "U+001B") {
+        this._closePopup();
+        event.stopPropagation();
+        event.preventDefault();
+    } else if (key == "Enter") {
+        this._handleYearMonthChange();
+        event.stopPropagation();
+        event.preventDefault();
+    }
+}
+
+YearMonthController.prototype._handleYearMonthChange = function() {
+    this._closePopup();
+
+    var result = this._monthPopup.value.match(/(\d+)-(\d+)/);
+    if (!result)
+        return;
+    var newYear = Number(result[1]);
+    var newMonth = Number(result[2]);
+    global.daysTable.navigateToMonthAndKeepSelectionPosition(newYear, newMonth);
+};
+
+/*
+ * @const
+ * @type {number}
+ */
+YearMonthController.PreviousTenYears = -120;
+/*
+ * @const
+ * @type {number}
+ */
+YearMonthController.PreviousYear = -12;
+/*
+ * @const
+ * @type {number}
+ */
+YearMonthController.PreviousMonth = -1;
+/*
+ * @const
+ * @type {number}
+ */
+YearMonthController.NextMonth = 1;
+/*
+ * @const
+ * @type {number}
+ */
+YearMonthController.NextYear = 12;
+/*
+ * @const
+ * @type {number}
+ */
+YearMonthController.NextTenYears = 120;
+
+/**
+ * @param {Event} event
+ */
+YearMonthController.prototype._handleButtonClick = function(event) {
+    if (event.target == this._left3)
+        this.moveRelatively(YearMonthController.PreviousTenYears);
+    else if (event.target == this._left2)
+        this.moveRelatively(YearMonthController.PreviousYear);
+    else if (event.target == this._left1)
+        this.moveRelatively(YearMonthController.PreviousMonth);
+    else if (event.target == this._right1)
+        this.moveRelatively(YearMonthController.NextMonth)
+    else if (event.target == this._right2)
+        this.moveRelatively(YearMonthController.NextYear);
+    else if (event.target == this._right3)
+        this.moveRelatively(YearMonthController.NextTenYears);
+    else
+        return;
+};
+
+/**
+ * @param {!number} amount
+ */
+YearMonthController.prototype.moveRelatively = function(amount) {
+    var min = global.minimumDate.getUTCFullYear() * 12 + global.minimumDate.getUTCMonth();
+    var max = global.maximumDate.getUTCFullYear() * 12 + global.maximumDate.getUTCMonth();
+    var current = this._currentYear * 12 + this._currentMonth;
+    var updated = current;
+    if (amount < 0)
+        updated = current + amount >= min ? current + amount : min;
+    else
+        updated = current + amount <= max ? current + amount : max;
+    if (updated == current)
+        return;
+    global.daysTable.navigateToMonthAndKeepSelectionPosition(Math.floor(updated / 12), updated % 12);
+};
+
+// ----------------------------------------------------------------
+
+/**
+ * @constructor
+ */
+function DaysTable() {
+    /**
+     * @type {!number}
+     */
+    this._x = -1;
+    /**
+     * @type {!number}
+     */
+    this._y = -1;
+    /**
+     * @type {!number}
+     */
+    this._currentYear = -1;
+    /**
+     * @type {!number}
+     */
+    this._currentMonth = -1;
+}
+
+/**
+ * @return {!boolean}
+ */
+DaysTable.prototype._hasSelection = function() {
+    return this._x >= 0 && this._y >= 0;
+}
+
+/**
+ * The number of week lines in the screen.
+ * @const
+ * @type {number}
+ */
+DaysTable._Weeks = 6;
+
+/**
+ * @param {!Element} main
+ */
+DaysTable.prototype.attachTo = function(main) {
+    this._daysContainer = createElement("table", ClassNames.DaysArea);
+    this._daysContainer.addEventListener("click", bind(this._handleDayClick, this), false);
+    this._daysContainer.addEventListener("mouseover", bind(this._handleMouseOver, this), false);
+    this._daysContainer.addEventListener("mouseout", bind(this._handleMouseOut, this), false);
+    this._daysContainer.addEventListener("webkitTransitionEnd", bind(this._moveInDays, this), false);
+    var container = createElement("tr", ClassNames.DayLabelContainer);
+    var weekStartDay = global.params.weekStartDay || 0;
+    for (var i = 0; i < 7; i++)
+        container.appendChild(createElement("th", ClassNames.DayLabel, global.params.dayLabels[(weekStartDay + i) % 7]));
+    this._daysContainer.appendChild(container);
+    this._days = [];
+    for (var w = 0; w < DaysTable._Weeks; w++) {
+        container = createElement("tr", ClassNames.WeekContainer);
+        var week = [];
+        for (var d = 0; d < 7; d++) {
+            var day = createElement("td", ClassNames.Day, " ");
+            day.setAttribute("data-position-x", String(d));
+            day.setAttribute("data-position-y", String(w));
+            week.push(day);
+            container.appendChild(day);
+        }
+        this._days.push(week);
+        this._daysContainer.appendChild(container);
+    }
+    container = createElement("div", ClassNames.DaysAreaContainer);
+    container.appendChild(this._daysContainer);
+    container.tabIndex = 0;
+    container.addEventListener("keydown", bind(this._handleKey, this), false);
+    main.appendChild(container);
+
+    container.focus();
+};
+
+/**
+ * @param {!number} time date in millisecond.
+ * @return {!boolean}
+ */
+function stepMismatch(time) {
+    return (time - global.minimumDate.getTime()) % global.step != 0;
+}
+
+/**
+ * @param {!number} time date in millisecond.
+ * @return {!boolean}
+ */
+function outOfRange(time) {
+    return time < global.minimumDate.getTime() || time > global.maximumDate.getTime();
+}
+
+/**
+ * @param {!number} time date in millisecond.
+ * @return {!boolean}
+ */
+function isValidDate(time) {
+    return !outOfRange(time) && !stepMismatch(time);
+}
+
+/**
+ * @param {!number} year
+ * @param {!number} month
+ */
+DaysTable.prototype._renderMonth = function(year, month) {
+    this._currentYear = year;
+    this._currentMonth = month;
+    var dayIterator = new Date(Date.UTC(year, month, 1));
+    dayIterator.setUTCFullYear(year);
+    var monthStartDay = dayIterator.getUTCDay();
+    var weekStartDay = global.params.weekStartDay || 0;
+    var startOffset = weekStartDay - monthStartDay;
+    if (startOffset >= 0)
+        startOffset -= 7;
+    dayIterator.setUTCDate(startOffset + 1);
+    for (var w = 0; w < DaysTable._Weeks; w++) {
+        for (var d = 0; d < 7; d++) {
+            var iterYear = dayIterator.getUTCFullYear();
+            var iterMonth = dayIterator.getUTCMonth();
+            var time = dayIterator.getTime();
+            var element = this._days[w][d];
+            // FIXME: Need localized number?
+            element.innerText = String(dayIterator.getUTCDate());
+            element.className = ClassNames.Day;
+            element.dataset.submitValue = serializeDate(iterYear, iterMonth, dayIterator.getUTCDate());
+            if (outOfRange(time))
+                element.classList.add(ClassNames.Unavailable);
+            else if (stepMismatch(time))
+                element.classList.add(ClassNames.Unavailable);
+            else if ((iterYear == year && dayIterator.getUTCMonth() < month) || (month == 0 && iterMonth == 11)) {
+                element.classList.add(ClassNames.Available);
+                element.classList.add(ClassNames.NotThisMonth);
+            } else if ((iterYear == year && dayIterator.getUTCMonth() > month) || (month == 11 && iterMonth == 0)) {
+                element.classList.add(ClassNames.Available);
+                element.classList.add(ClassNames.NotThisMonth);
+            } else if (isNaN(time)) {
+                element.innerText = "-";
+                element.classList.add(ClassNames.Unavailable);
+            } else
+                element.classList.add(ClassNames.Available);
+            dayIterator.setUTCDate(dayIterator.getUTCDate() + 1);
+        }
+    }
+
+    global.today.disabled = !isValidDate(parseDateString().getTime());
+};
+
+/**
+ * @param {!number} year
+ * @param {!number} month
+ */
+DaysTable.prototype._navigateToMonth = function(year, month) {
+    global.yearMonthController.setYearMonth(year, month);
+    this._renderMonth(year, month);
+};
+
+/**
+ * @param {!number} year
+ * @param {!number} month
+ */
+DaysTable.prototype._navigateToMonthWithAnimation = function(year, month) {
+    if (this._currentYear >= 0 && this._currentMonth >= 0) {
+        if (year == this._currentYear && month == this._currentMonth)
+            return;
+        var decreasing = false;
+        if (year < this._currentYear)
+            decreasing = true;
+        else if (year > this._currentYear)
+            decreasing = false;
+        else
+            decreasing = month < this._currentMonth;
+        var daysStyle = this._daysContainer.style;
+        daysStyle.position = "relative";
+        daysStyle.webkitTransition = "left 0.1s ease";
+        daysStyle.left = (decreasing ? "" : "-") + this._daysContainer.offsetWidth + "px";
+    }
+    this._navigateToMonth(year, month);
+};
+
+DaysTable.prototype._moveInDays = function() {
+    var daysStyle = this._daysContainer.style;
+    if (daysStyle.left == "0px")
+        return;
+    daysStyle.webkitTransition = "";
+    daysStyle.left = (daysStyle.left.charAt(0) == "-" ? "" : "-") + this._daysContainer.offsetWidth + "px";
+    this._daysContainer.offsetLeft; // Force to layout.
+    daysStyle.webkitTransition = "left 0.1s ease";
+    daysStyle.left = "0px";
+};
+
+/**
+ * @param {!number} year
+ * @param {!number} month
+ */
+DaysTable.prototype.navigateToMonthAndKeepSelectionPosition = function(year, month) {
+    if (year == this._currentYear && month == this._currentMonth)
+        return;
+    this._navigateToMonthWithAnimation(year, month);
+    if (this._hasSelection())
+        this._days[this._y][this._x].classList.add(ClassNames.Selected);
+};
+
+/**
+ * @param {!Date} date
+ */
+DaysTable.prototype.selectDate = function(date) {
+    this._navigateToMonthWithAnimation(date.getUTCFullYear(), date.getUTCMonth());
+    var dateString = serializeDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
+    for (var w = 0; w < DaysTable._Weeks; w++) {
+        for (var d = 0; d < 7; d++) {
+            if (this._days[w][d].dataset.submitValue == dateString) {
+                this._days[w][d].classList.add(ClassNames.Selected);
+                this._x = d;
+                this._y = w;
+                break;
+            }
+        }
+    }
+};
+
+/**
+ * @return {!boolean}
+ */
+DaysTable.prototype._maybeSetPreviousMonth = function() {
+    var year = global.yearMonthController.year();
+    var month = global.yearMonthController.month();
+    var thisMonthStartTime = Date.UTC(year, month, 1);
+    if (global.minimumDate.getTime() >= thisMonthStartTime)
+        return false;
+    if (month == 0) {
+        year--;
+        month = 11;
+    } else
+        month--;
+    this._navigateToMonthWithAnimation(year, month);
+    return true;
+};
+
+/**
+ * @return {!boolean}
+ */
+DaysTable.prototype._maybeSetNextMonth = function() {
+    var year = global.yearMonthController.year();
+    var month = global.yearMonthController.month();
+    if (month == 11) {
+        year++;
+        month = 0;
+    } else
+        month++;
+    var nextMonthStartTime = Date.UTC(year, month, 1);
+    if (global.maximumDate.getTime() < nextMonthStartTime)
+        return false;
+    this._navigateToMonthWithAnimation(year, month);
+    return true;
+};
+
+/**
+ * @param {Event} event
+ */
+DaysTable.prototype._handleDayClick = function(event) {
+    if (event.target.classList.contains(ClassNames.Available))
+        submitValue(event.target.dataset.submitValue);
+};
+
+/**
+ * @param {Event} event
+ */
+DaysTable.prototype._handleMouseOver = function(event) {
+    var node = event.target;
+    if (this._hasSelection())
+        this._days[this._y][this._x].classList.remove(ClassNames.Selected);
+    if (!node.classList.contains(ClassNames.Day)) {
+        this._x = -1;
+        this._y = -1;
+        return;
+    }
+    node.classList.add(ClassNames.Selected);
+    this._x = Number(node.dataset.positionX);
+    this._y = Number(node.dataset.positionY);
+};
+
+/**
+ * @param {Event} event
+ */
+DaysTable.prototype._handleMouseOut = function(event) {
+    if (this._hasSelection())
+        this._days[this._y][this._x].classList.remove(ClassNames.Selected);
+    this._x = -1;
+    this._y = -1;
+};
+
+/**
+ * @param {Event} event
+ */
+DaysTable.prototype._handleKey = function(event) {
+    maybeUpdateFocusStyle();
+    var x = this._x;
+    var y = this._y;
+    var key = event.keyIdentifier;
+    if (!this._hasSelection() && (key == "Left" || key == "Up" || key == "Right" || key == "Down")) {
+        // Put the selection on a center cell.
+        this.updateSelection(event, 3, Math.floor(DaysTable._Weeks / 2 - 1));
+        return;
+    }
+
+    if (key == "Left") {
+        if (x == 0) {
+            if (y == 0) {
+                if (!this._maybeSetPreviousMonth())
+                    return;
+                y = DaysTable._Weeks - 1;
+            } else
+                y--;
+            x = 6;
+        } else
+            x--;
+        this.updateSelection(event, x, y);
+
+    } else if (key == "Up") {
+        if (y == 0) {
+            if (!this._maybeSetPreviousMonth())
+                return;
+            y = DaysTable._Weeks - 1;
+        } else
+            y--;
+        this.updateSelection(event, x, y);
+
+    } else if (key == "Right") {
+        if (x == 6) {
+            if (y == DaysTable._Weeks - 1) {
+                if (!this._maybeSetNextMonth())
+                    return;
+                y = 0;
+            } else
+                y++;
+            x = 0;
+        } else
+            x++;
+        this.updateSelection(event, x, y);
+
+    } else if (key == "Down") {
+        if (y == DaysTable._Weeks - 1) {
+            if (!this._maybeSetNextMonth())
+                return;
+            y = 0;
+        } else
+            y++;
+        this.updateSelection(event, x, y);
+
+    } else if (key == "PageUp") {
+        if (!this._maybeSetPreviousMonth())
+            return;
+        this.updateSelection(event, x, y);
+
+    } else if (key == "PageDown") {
+        if (!this._maybeSetNextMonth())
+            return;
+        this.updateSelection(event, x, y);
+
+    } else if (this._hasSelection() && key == "Enter") {
+        var dayNode = this._days[y][x];
+        if (dayNode.classList.contains(ClassNames.Available)) {
+            submitValue(dayNode.dataset.submitValue);
+            event.stopPropagation();
+        }
+
+    } else if (key == "U+0054") { // 't'
+        this._days[this._y][this._x].classList.remove(ClassNames.Selected);
+        this.selectDate(new Date());
+        event.stopPropagation();
+        event.preventDefault();
+    }
+};
+
+/**
+ * @param {Event} event
+ * @param {!number} x
+ * @param {!number} y
+ */
+DaysTable.prototype.updateSelection = function(event, x, y) {
+    if (this._hasSelection())
+        this._days[this._y][this._x].classList.remove(ClassNames.Selected);
+    if (x >= 0 && y >= 0) {
+        this._days[y][x].classList.add(ClassNames.Selected);
+        this._x = x;
+        this._y = y;
+    }
+    event.stopPropagation();
+    event.preventDefault();
+};
+
+// ----------------------------------------------------------------
+
+function handleToday() {
+    var date = new Date();
+    global.daysTable.selectDate(date);
+    submitValue(serializeDate(date.getFullYear(), date.getMonth(), date.getDate()));
+}
+
+function handleClear() {
+    submitValue("");
+}
+
+/**
+ * @param {string} value
+ */
+function submitValue(value) {
+    window.setValueAndClosePopup(0, value);
+}
+
+function handleCancel() {
+    window.setValueAndClosePopup(-1, "");
+}
+
+/**
+ * @param {Event} event
+ */
+function handleGlobalKey(event) {
+    maybeUpdateFocusStyle();
+    var key = event.keyIdentifier;
+    if (key == "U+0009") {
+        if (!event.shiftKey && document.activeElement == global.lastFocusableControl) {
+            event.stopPropagation();
+            event.preventDefault();
+            global.firstFocusableControl.focus();
+        } else if (event.shiftKey && document.activeElement == global.firstFocusableControl) {
+            event.stopPropagation();
+            event.preventDefault();
+            global.lastFocusableControl.focus();
+        }
+    } else if (key == "U+004D") { // 'm'
+        global.yearMonthController.moveRelatively(event.shiftKey ? YearMonthController.PreviousMonth : YearMonthController.NextMonth);
+    } else if (key == "U+0059") { // 'y'
+        global.yearMonthController.moveRelatively(event.shiftKey ? YearMonthController.PreviousYear : YearMonthController.NextYear);
+    } else if (key == "U+0044") { // 'd'
+        global.yearMonthController.moveRelatively(event.shiftKey ? YearMonthController.PreviousTenYears : YearMonthController.NextTenYears);
+    }
+}
+
+function maybeUpdateFocusStyle() {
+    if (global.hadKeyEvent)
+        return;
+    global.hadKeyEvent = true;
+    $("main").classList.remove(ClassNames.NoFocusRing);
+}
+
+if (window.dialogArguments) {
+    initialize(dialogArguments);
+} else {
+    window.addEventListener("message", handleMessage, false);
+    window.setTimeout(handleArgumentsTimeout, 1000);
+}
index f64e71b..065790a 100644 (file)
           ],
         },
         {
+          'action_name': 'CalendarPicker',
+          'inputs': [
+            '../Resources/calendarPicker.css',
+            '../Resources/calendarPicker.js',
+          ],
+          'outputs': [
+            '<(SHARED_INTERMEDIATE_DIR)/webkit/CalendarPicker.h',
+            '<(SHARED_INTERMEDIATE_DIR)/webkit/CalendarPicker.cpp',
+          ],
+          'action': [
+            'python',
+            '../make-file-arrays.py',
+            '--condition=ENABLE(CALENDAR_PICKER)',
+            '--out-h=<(SHARED_INTERMEDIATE_DIR)/webkit/CalendarPicker.h',
+            '--out-cpp=<(SHARED_INTERMEDIATE_DIR)/webkit/CalendarPicker.cpp',
+            '<@(_inputs)',
+          ],
+        },
+        {
           'action_name': 'XLinkNames',
           'inputs': [
             '../dom/make_names.pl',
         # Additional .cpp files from webcore_bindings_sources actions.
         '<(SHARED_INTERMEDIATE_DIR)/webkit/HTMLElementFactory.cpp',
         '<(SHARED_INTERMEDIATE_DIR)/webkit/HTMLNames.cpp',
+        '<(SHARED_INTERMEDIATE_DIR)/webkit/CalendarPicker.cpp',
         '<(SHARED_INTERMEDIATE_DIR)/webkit/EventFactory.cpp',
         '<(SHARED_INTERMEDIATE_DIR)/webkit/EventHeaders.h',
         '<(SHARED_INTERMEDIATE_DIR)/webkit/EventInterfaces.h',