summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--accessible/tests/mochitest/jsat/test_output.html9
-rw-r--r--application/palemoon/base/content/abouthome/aboutHome.js4
-rw-r--r--application/palemoon/base/content/browser-addons.js4
-rw-r--r--application/palemoon/base/content/browser-gestureSupport.js4
-rw-r--r--application/palemoon/base/content/browser-places.js4
-rw-r--r--application/palemoon/base/content/browser-syncui.js2
-rw-r--r--application/palemoon/base/content/browser-thumbnails.js2
-rw-r--r--application/palemoon/base/content/browser-uacompat.js45
-rw-r--r--application/palemoon/base/content/browser-webrtcUI.js2
-rw-r--r--application/palemoon/base/content/browser.css12
-rw-r--r--application/palemoon/base/content/browser.js12
-rw-r--r--application/palemoon/base/content/browser.xul18
-rw-r--r--application/palemoon/base/content/newtab/drag.js2
-rw-r--r--application/palemoon/base/content/newtab/dragDataHelper.js2
-rw-r--r--application/palemoon/base/content/newtab/drop.js2
-rw-r--r--application/palemoon/base/content/newtab/dropPreview.js2
-rw-r--r--application/palemoon/base/content/newtab/dropTargetShim.js2
-rw-r--r--application/palemoon/base/content/newtab/grid.js2
-rw-r--r--application/palemoon/base/content/newtab/newTab.js2
-rw-r--r--application/palemoon/base/content/newtab/page.js2
-rw-r--r--application/palemoon/base/content/newtab/transformations.js2
-rw-r--r--application/palemoon/base/content/newtab/undo.js2
-rw-r--r--application/palemoon/base/content/newtab/updater.js2
-rw-r--r--application/palemoon/base/content/popup-notifications.inc1
-rw-r--r--application/palemoon/base/content/sync/aboutSyncTabs.js2
-rw-r--r--application/palemoon/base/content/sync/addDevice.js2
-rw-r--r--application/palemoon/base/content/sync/genericChange.js2
-rw-r--r--application/palemoon/base/content/sync/progress.js4
-rw-r--r--application/palemoon/base/content/sync/quota.js4
-rw-r--r--application/palemoon/base/content/sync/utils.js2
-rw-r--r--application/palemoon/base/content/tabbrowser.xml24
-rw-r--r--application/palemoon/base/content/urlbarBindings.xml189
-rw-r--r--application/palemoon/branding/official/pref/palemoon-branding.js2
-rw-r--r--application/palemoon/branding/unstable/pref/palemoon-branding.js2
-rw-r--r--application/palemoon/components/downloads/DownloadsCommon.jsm2
-rw-r--r--application/palemoon/components/downloads/content/contentAreaDownloadsView.js2
-rw-r--r--application/palemoon/components/nsBrowserGlue.js4
-rw-r--r--application/palemoon/components/places/BrowserPlaces.manifest2
-rw-r--r--application/palemoon/components/places/PlacesProtocolHandler.js49
-rw-r--r--application/palemoon/components/places/content/controller.js2
-rw-r--r--application/palemoon/components/places/content/places.js4
-rw-r--r--application/palemoon/components/places/moz.build4
-rw-r--r--application/palemoon/components/preferences/aboutPermissions.js14
-rw-r--r--application/palemoon/components/preferences/sanitize.js2
-rw-r--r--application/palemoon/components/preferences/sync.js2
-rw-r--r--application/palemoon/components/sessionstore/SessionStorage.jsm4
-rw-r--r--application/palemoon/components/sessionstore/SessionStore.jsm12
-rw-r--r--application/palemoon/components/sessionstore/_SessionFile.jsm2
-rw-r--r--application/palemoon/components/sessionstore/nsSessionStartup.js2
-rw-r--r--application/palemoon/components/shell/ShellService.jsm2
-rw-r--r--application/palemoon/installer/Makefile.in13
-rw-r--r--application/palemoon/locales/en-US/chrome/browser/browser.properties20
-rw-r--r--application/palemoon/locales/en-US/chrome/overrides/netError.dtd3
-rw-r--r--application/palemoon/modules/BrowserNewTabPreloader.jsm8
-rw-r--r--application/palemoon/modules/FormSubmitObserver.jsm16
-rw-r--r--application/palemoon/modules/FormValidationHandler.jsm10
-rw-r--r--application/palemoon/modules/NetworkPrioritizer.jsm8
-rw-r--r--application/palemoon/modules/PopupNotifications.jsm4
-rw-r--r--application/palemoon/modules/SharedFrame.jsm2
-rw-r--r--application/palemoon/modules/openLocationLastURL.jsm8
-rw-r--r--application/palemoon/modules/promise.js2
-rw-r--r--application/palemoon/themes/linux/browser.css23
-rw-r--r--application/palemoon/themes/linux/jar.mn1
-rw-r--r--application/palemoon/themes/linux/sync-notification-24.pngbin1565 -> 0 bytes
-rw-r--r--application/palemoon/themes/osx/browser.css35
-rw-r--r--application/palemoon/themes/osx/jar.mn1
-rw-r--r--application/palemoon/themes/osx/sync-notification-24.pngbin1119 -> 0 bytes
-rw-r--r--application/palemoon/themes/windows/browser.css36
-rw-r--r--application/palemoon/themes/windows/jar.mn1
-rw-r--r--application/palemoon/themes/windows/sync-notification-24.pngbin1119 -> 0 bytes
-rw-r--r--browser/base/content/browser.css3
-rw-r--r--browser/base/content/browser.xul8
-rw-r--r--docshell/test/unit/test_nsDefaultURIFixup_info.js6
-rw-r--r--docshell/test/unit/test_nsDefaultURIFixup_search.js2
-rw-r--r--dom/canvas/CanvasRenderingContext2D.cpp24
-rw-r--r--dom/canvas/CanvasRenderingContext2D.h4
-rw-r--r--dom/html/HTMLInputElement.cpp443
-rw-r--r--dom/html/HTMLInputElement.h96
-rw-r--r--dom/html/nsIFormControl.h2
-rw-r--r--dom/html/test/forms/mochitest.ini8
-rw-r--r--dom/html/test/forms/test_input_date_key_events.html228
-rw-r--r--dom/html/test/forms/test_input_datetime_focus_blur.html28
-rw-r--r--dom/html/test/forms/test_input_datetime_focus_blur_events.html90
-rw-r--r--dom/html/test/forms/test_input_datetime_input_change_events.html88
-rw-r--r--dom/html/test/forms/test_input_datetime_tabindex.html47
-rw-r--r--dom/html/test/forms/test_input_time_focus_blur_events.html82
-rw-r--r--dom/html/test/forms/test_input_types_pref.html48
-rw-r--r--dom/html/test/forms/test_input_typing_sanitization.html28
-rw-r--r--dom/html/test/forms/test_max_attribute.html47
-rw-r--r--dom/html/test/forms/test_min_attribute.html49
-rw-r--r--dom/html/test/forms/test_step_attribute.html102
-rw-r--r--dom/html/test/forms/test_stepup_stepdown.html174
-rw-r--r--dom/html/test/forms/test_valueAsDate_pref.html8
-rw-r--r--dom/html/test/forms/test_valueasdate_attribute.html108
-rw-r--r--dom/html/test/forms/test_valueasnumber_attribute.html123
-rw-r--r--dom/html/test/test_bug558788-1.html5
-rw-r--r--dom/ipc/DatePickerParent.cpp87
-rw-r--r--dom/ipc/DatePickerParent.h61
-rw-r--r--dom/ipc/PBrowser.ipdl8
-rw-r--r--dom/ipc/PDatePicker.ipdl27
-rw-r--r--dom/ipc/TabChild.cpp16
-rw-r--r--dom/ipc/TabChild.h4
-rw-r--r--dom/ipc/TabParent.cpp15
-rw-r--r--dom/ipc/TabParent.h4
-rw-r--r--dom/ipc/moz.build2
-rw-r--r--dom/locales/en-US/chrome/layout/HtmlForm.properties1
-rw-r--r--dom/url/tests/test_url.html12
-rw-r--r--dom/webidl/CanvasRenderingContext2D.webidl3
-rw-r--r--dom/webidl/HTMLInputElement.webidl19
-rw-r--r--editor/libeditor/HTMLEditorDataTransfer.cpp31
-rw-r--r--editor/libeditor/tests/mochitest.ini3
-rw-r--r--editor/libeditor/tests/test_bug1306532.html64
-rw-r--r--extensions/cookie/test/unit/test_bug526789.js12
-rw-r--r--js/public/Date.h8
-rw-r--r--js/src/builtin/Intl.cpp381
-rw-r--r--js/src/builtin/Intl.h54
-rw-r--r--js/src/js.msg2
-rwxr-xr-xjs/src/jsdate.cpp12
-rw-r--r--js/src/shell/js.cpp1
-rw-r--r--js/src/tests/Intl/getDisplayNames.js238
-rw-r--r--js/src/vm/SelfHosting.cpp1
-rw-r--r--layout/base/nsCSSFrameConstructor.cpp4
-rw-r--r--layout/forms/nsDateTimeControlFrame.cpp3
-rw-r--r--layout/reftests/forms/input/datetime/reftest.list11
-rw-r--r--layout/reftests/forms/input/datetime/time-content-left-aligned-ref.html9
-rw-r--r--layout/reftests/forms/input/datetime/time-content-left-aligned.html9
-rw-r--r--layout/reftests/forms/input/datetime/time-reset-button-right-aligned-ref.html9
-rw-r--r--layout/reftests/forms/input/datetime/time-reset-button-right-aligned.html10
-rw-r--r--layout/reftests/forms/input/datetime/time-small-height-ref.html18
-rw-r--r--layout/reftests/forms/input/datetime/time-small-height.html19
-rw-r--r--layout/reftests/forms/input/datetime/time-small-width-height-ref.html18
-rw-r--r--layout/reftests/forms/input/datetime/time-small-width-height.html19
-rw-r--r--layout/reftests/forms/input/datetime/time-small-width-ref.html19
-rw-r--r--layout/reftests/forms/input/datetime/time-small-width.html20
-rw-r--r--layout/style/res/forms.css5
-rw-r--r--layout/style/res/html.css5
-rw-r--r--modules/libpref/init/all.js7
-rw-r--r--netwerk/base/nsStandardURL.cpp43
-rw-r--r--netwerk/protocol/http/nsHttpHandler.h2
-rw-r--r--netwerk/test/unit/test_URIs.js26
-rw-r--r--netwerk/test/unit/test_standardurl.js39
-rw-r--r--services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js2
-rw-r--r--services/fxaccounts/tests/xpcshell/test_profile_client.js2
-rw-r--r--testing/marionette/interaction.js50
-rw-r--r--testing/marionette/listener.js4
-rw-r--r--testing/profiles/prefs_general.js1
-rw-r--r--testing/web-platform/meta/MANIFEST.json12
-rw-r--r--testing/web-platform/meta/XMLHttpRequest/open-url-bogus.htm.ini11
-rw-r--r--testing/web-platform/meta/html/semantics/forms/constraints/form-validation-checkValidity.html.ini22
-rw-r--r--testing/web-platform/meta/html/semantics/forms/constraints/form-validation-reportValidity.html.ini21
-rw-r--r--testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.html.ini18
-rw-r--r--testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.html.ini18
-rw-r--r--testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-stepMismatch.html.ini4
-rw-r--r--testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-valid.html.ini13
-rw-r--r--testing/web-platform/meta/html/semantics/selectors/pseudo-classes/inrange-outofrange.html.ini20
-rw-r--r--testing/web-platform/meta/url/url-constructor.html.ini3
-rw-r--r--testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1.html (renamed from testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_html_image_1.html)6
-rw-r--r--testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1_ref.html (renamed from testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_html_image_1_ref.html)0
-rw-r--r--toolkit/components/mozintl/MozIntl.cpp26
-rw-r--r--toolkit/components/mozintl/mozIMozIntl.idl1
-rw-r--r--toolkit/components/mozintl/test/test_mozintl.js14
-rw-r--r--toolkit/components/passwordmgr/test/unit/test_logins_search.js1
-rw-r--r--toolkit/components/places/UnifiedComplete.js2
-rw-r--r--toolkit/components/places/nsNavHistory.cpp4
-rw-r--r--toolkit/components/satchel/test/test_form_autocomplete.html12
-rw-r--r--toolkit/content/browser-content.js16
-rw-r--r--toolkit/content/customizeToolbar.js24
-rw-r--r--toolkit/content/datepicker.xhtml60
-rw-r--r--toolkit/content/jar.mn4
-rw-r--r--toolkit/content/tests/browser/browser.ini1
-rw-r--r--toolkit/content/tests/browser/browser_datetime_datepicker.js284
-rw-r--r--toolkit/content/tests/browser/head.js90
-rw-r--r--toolkit/content/timepicker.xhtml2
-rw-r--r--toolkit/content/widgets/calendar.js171
-rw-r--r--toolkit/content/widgets/datekeeper.js336
-rw-r--r--toolkit/content/widgets/datepicker.js376
-rw-r--r--toolkit/content/widgets/datetimebox.css12
-rw-r--r--toolkit/content/widgets/datetimebox.xml782
-rw-r--r--toolkit/content/widgets/datetimepicker.xml6
-rw-r--r--toolkit/content/widgets/datetimepopup.xml171
-rw-r--r--toolkit/content/widgets/spinner.js47
-rw-r--r--toolkit/content/widgets/timekeeper.js12
-rw-r--r--toolkit/content/widgets/timepicker.js39
-rw-r--r--toolkit/content/widgets/toolbar.xml53
-rw-r--r--toolkit/locales/en-US/chrome/global/customizeToolbar.properties1
-rw-r--r--toolkit/locales/en-US/chrome/global/datetimebox.dtd9
-rw-r--r--toolkit/locales/jar.mn1
-rw-r--r--toolkit/modules/DateTimePickerHelper.jsm29
-rw-r--r--toolkit/mozapps/extensions/AddonManager.jsm6
-rw-r--r--toolkit/mozapps/extensions/DeferredSave.jsm6
-rw-r--r--toolkit/mozapps/extensions/addonManager.js4
-rw-r--r--toolkit/mozapps/extensions/amInstallTrigger.js2
-rw-r--r--toolkit/mozapps/extensions/amWebInstallListener.js2
-rw-r--r--toolkit/mozapps/extensions/content/extensions.js2
-rw-r--r--toolkit/mozapps/extensions/content/update.js4
-rw-r--r--toolkit/mozapps/extensions/internal/AddonRepository.jsm4
-rw-r--r--toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm2
-rw-r--r--toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm2
-rw-r--r--toolkit/mozapps/extensions/internal/Content.js4
-rw-r--r--toolkit/mozapps/extensions/internal/GMPProvider.jsm8
-rw-r--r--toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm8
-rw-r--r--toolkit/mozapps/extensions/internal/PluginProvider.jsm2
-rw-r--r--toolkit/mozapps/extensions/internal/XPIProvider.jsm4
-rw-r--r--toolkit/mozapps/extensions/internal/XPIProviderUtils.js2
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser-common.ini2
-rw-r--r--toolkit/mozapps/webextensions/test/browser/browser-common.ini1
-rw-r--r--toolkit/themes/shared/datetimeinputpickers.css377
-rw-r--r--toolkit/themes/shared/datetimepopup.css11
-rw-r--r--toolkit/themes/shared/icons/calendar-arrows.svg13
-rw-r--r--toolkit/themes/shared/icons/spinner-arrows.svg13
-rw-r--r--toolkit/themes/shared/jar.inc.mn5
-rw-r--r--toolkit/themes/shared/timepicker.css153
-rw-r--r--widget/moz.build2
-rw-r--r--widget/nsContentProcessWidgetFactory.cpp6
-rw-r--r--widget/nsDatePickerProxy.cpp61
-rw-r--r--widget/nsDatePickerProxy.h33
-rw-r--r--widget/nsIDatePicker.idl50
-rw-r--r--widget/nsWidgetsCID.h5
-rw-r--r--xpcom/io/nsEscape.cpp2
219 files changed, 5637 insertions, 1838 deletions
diff --git a/accessible/tests/mochitest/jsat/test_output.html b/accessible/tests/mochitest/jsat/test_output.html
index ec2b289be..525642607 100644
--- a/accessible/tests/mochitest/jsat/test_output.html
+++ b/accessible/tests/mochitest/jsat/test_output.html
@@ -125,14 +125,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984
{"string": "listAbbr"},
{"string": "cellInfoAbbr", "args": [ 1, 1]}]]
}, {
- accOrElmOrID: "date",
- expectedUtterance: [[{"string": "textInputType_date"},
- {"string": "entry"}, "2011-09-29"], ["2011-09-29",
- {"string": "textInputType_date"}, {"string": "entry"}]],
- expectedBraille: [[{"string": "textInputType_date"},
- {"string": "entryAbbr"}, "2011-09-29"], ["2011-09-29",
- {"string": "textInputType_date"}, {"string": "entryAbbr"}]]
- }, {
accOrElmOrID: "email",
expectedUtterance: [[{"string": "textInputType_email"},
{"string": "entry"}, "test@example.com"], ["test@example.com",
@@ -619,7 +611,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984
<label for="password">Secret Password</label><input id="password" type="password"></input>
<label for="radio_unselected">any old radio button</label><input id="radio_unselected" type="radio"></input>
<label for="radio_selected">a unique radio button</label><input id="radio_selected" type="radio" checked></input>
- <input id="date" type="date" value="2011-09-29" />
<input id="email" type="email" value="test@example.com" />
<input id="search" type="search" value="This is a search" />
<input id="tel" type="tel" value="555-5555" />
diff --git a/application/palemoon/base/content/abouthome/aboutHome.js b/application/palemoon/base/content/abouthome/aboutHome.js
index 9e826b69e..6a970acd6 100644
--- a/application/palemoon/base/content/abouthome/aboutHome.js
+++ b/application/palemoon/base/content/abouthome/aboutHome.js
@@ -234,8 +234,8 @@ const SEARCH_ENGINES = {
};
// This global tracks if the page has been set up before, to prevent double inits
-let gInitialized = false;
-let gObserver = new MutationObserver(function (mutations) {
+var gInitialized = false;
+var gObserver = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
if (mutation.attributeName == "searchEngineURL") {
setupSearchEngine();
diff --git a/application/palemoon/base/content/browser-addons.js b/application/palemoon/base/content/browser-addons.js
index 7993a0c9c..f5c398f33 100644
--- a/application/palemoon/base/content/browser-addons.js
+++ b/application/palemoon/base/content/browser-addons.js
@@ -226,7 +226,7 @@ const gXPInstallObserver = {
* - If an add-on was installed, incrementing the count, show the bar.
* - If an add-on was uninstalled, and no more items are left, hide the bar.
*/
-let AddonsMgrListener = {
+var AddonsMgrListener = {
get addonBar() document.getElementById("addon-bar"),
get statusBar() document.getElementById("status-bar"),
getAddonBarItemCount: function() {
@@ -461,7 +461,7 @@ var LightWeightThemeWebInstaller = {
/*
* Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
*/
-let LightweightThemeListener = {
+var LightweightThemeListener = {
_modifiedStyles: [],
init: function () {
diff --git a/application/palemoon/base/content/browser-gestureSupport.js b/application/palemoon/base/content/browser-gestureSupport.js
index d88f47c79..13eb71b99 100644
--- a/application/palemoon/base/content/browser-gestureSupport.js
+++ b/application/palemoon/base/content/browser-gestureSupport.js
@@ -13,7 +13,7 @@
// chrome-only, we must listen for the simple gesture events during
// the capturing phase and call stopPropagation on every event.
-let gGestureSupport = {
+var gGestureSupport = {
_currentRotation: 0,
_lastRotateDelta: 0,
_rotateMomentumThreshold: .75,
@@ -532,7 +532,7 @@ let gGestureSupport = {
};
// History Swipe Animation Support (bug 678392)
-let gHistorySwipeAnimation = {
+var gHistorySwipeAnimation = {
active: false,
isLTR: false,
diff --git a/application/palemoon/base/content/browser-places.js b/application/palemoon/base/content/browser-places.js
index cf9c28597..5c13a43f9 100644
--- a/application/palemoon/base/content/browser-places.js
+++ b/application/palemoon/base/content/browser-places.js
@@ -959,7 +959,7 @@ var PlacesMenuDNDHandler = {
* This object handles the initialization and uninitialization of the bookmarks
* toolbar.
*/
-let PlacesToolbarHelper = {
+var PlacesToolbarHelper = {
_place: "place:folder=TOOLBAR",
get _viewElt() {
@@ -1006,7 +1006,7 @@ let PlacesToolbarHelper = {
* menu button.
*/
-let BookmarkingUI = {
+var BookmarkingUI = {
get button() {
if (!this._button) {
this._button = document.getElementById("bookmarks-menu-button");
diff --git a/application/palemoon/base/content/browser-syncui.js b/application/palemoon/base/content/browser-syncui.js
index fc8c7f016..67056e221 100644
--- a/application/palemoon/base/content/browser-syncui.js
+++ b/application/palemoon/base/content/browser-syncui.js
@@ -3,7 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
// gSyncUI handles updating the tools menu
-let gSyncUI = {
+var gSyncUI = {
_obs: ["weave:service:sync:start",
"weave:service:sync:delayed",
"weave:service:quota:remaining",
diff --git a/application/palemoon/base/content/browser-thumbnails.js b/application/palemoon/base/content/browser-thumbnails.js
index b06dfd503..079b0ac1b 100644
--- a/application/palemoon/base/content/browser-thumbnails.js
+++ b/application/palemoon/base/content/browser-thumbnails.js
@@ -7,7 +7,7 @@
/**
* Keeps thumbnails of open web pages up-to-date.
*/
-let gBrowserThumbnails = {
+var gBrowserThumbnails = {
/**
* Pref that controls whether we can store SSL content on disk
*/
diff --git a/application/palemoon/base/content/browser-uacompat.js b/application/palemoon/base/content/browser-uacompat.js
new file mode 100644
index 000000000..933aa55d1
--- /dev/null
+++ b/application/palemoon/base/content/browser-uacompat.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var UserAgentCompatibility = {
+ PREF_UA_COMPAT: "general.useragent.compatMode",
+ PREF_UA_COMPAT_GECKO: "general.useragent.compatMode.gecko",
+ PREF_UA_COMPAT_FIREFOX: "general.useragent.compatMode.firefox",
+
+ init: function() {
+ this.checkPreferences();
+ Services.prefs.addObserver(this.PREF_UA_COMPAT, this, false);
+ Services.prefs.addObserver(this.PREF_UA_COMPAT_GECKO, this, false);
+ Services.prefs.addObserver(this.PREF_UA_COMPAT_FIREFOX, this, false);
+ },
+
+ uninit: function() {
+ Services.prefs.removeObserver(this.PREF_UA_COMPAT, this, false);
+ Services.prefs.removeObserver(this.PREF_UA_COMPAT_GECKO, this, false);
+ Services.prefs.removeObserver(this.PREF_UA_COMPAT_FIREFOX, this, false);
+ },
+
+ observe: function() {
+ this.checkPreferences();
+ },
+
+ checkPreferences: function() {
+ var compatMode = Services.prefs.getIntPref(this.PREF_UA_COMPAT);
+
+ switch(compatMode) {
+ case 0:
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_GECKO, false);
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_FIREFOX, false);
+ break;
+ case 1:
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_GECKO, true);
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_FIREFOX, false);
+ break;
+ case 2:
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_GECKO, true);
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_FIREFOX, true);
+ break;
+ }
+ }
+};
diff --git a/application/palemoon/base/content/browser-webrtcUI.js b/application/palemoon/base/content/browser-webrtcUI.js
index a6c9008ca..d59134ce5 100644
--- a/application/palemoon/base/content/browser-webrtcUI.js
+++ b/application/palemoon/base/content/browser-webrtcUI.js
@@ -3,7 +3,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-let WebrtcIndicator = {
+var WebrtcIndicator = {
init: function () {
let temp = {};
Cu.import("resource:///modules/webrtcUI.jsm", temp);
diff --git a/application/palemoon/base/content/browser.css b/application/palemoon/base/content/browser.css
index e6a84421b..4865cfee7 100644
--- a/application/palemoon/base/content/browser.css
+++ b/application/palemoon/base/content/browser.css
@@ -280,6 +280,10 @@ panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .a
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#urlbar-rich-result-popup");
}
+#DateTimePickerPanel[active="true"] {
+ -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
+}
+
/* Pale Moon: Address bar: Feeds */
#ub-feed-button > .button-box > .box-inherit > .button-text,
#ub-feed-button > .button-box > .button-menu-dropmarker {
@@ -542,11 +546,11 @@ window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(#nav-bar):not(
max-width: 280px;
}
-.form-validation-anchor {
+.popup-anchor {
/* should occupy space but not be visible */
opacity: 0;
- visibility: hidden;
pointer-events: none;
+ -moz-stack-sizing: ignore;
}
#addon-progress-notification {
@@ -627,10 +631,6 @@ statuspanel[inactive][previoustype=overLink] {
-moz-box-align: end;
}
-.panel-inner-arrowcontentfooter[footertype="promobox"] {
- -moz-binding: url("chrome://browser/content/urlbarBindings.xml#promobox");
-}
-
/* highlighter */
%include highlighter.css
diff --git a/application/palemoon/base/content/browser.js b/application/palemoon/base/content/browser.js
index 3f294d82b..5fe475338 100644
--- a/application/palemoon/base/content/browser.js
+++ b/application/palemoon/base/content/browser.js
@@ -7,6 +7,7 @@ var Ci = Components.interfaces;
var Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
@@ -144,6 +145,7 @@ let gInitialPages = [
#include browser-plugins.js
#include browser-tabPreviews.js
#include browser-thumbnails.js
+#include browser-uacompat.js
#ifdef MOZ_WEBRTC
#include browser-webrtcUI.js
@@ -920,6 +922,7 @@ var gBrowserInit = {
TabsInTitlebar.init();
retrieveToolbarIconsizesFromTheme();
ToolbarIconColor.init();
+ UserAgentCompatibility.init();
#ifdef XP_WIN
if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
@@ -1296,6 +1299,8 @@ var gBrowserInit = {
DevToolsTheme.uninit();
#endif
+ UserAgentCompatibility.uninit();
+
var enumerator = Services.wm.getEnumerator(null);
enumerator.getNext();
if (!enumerator.hasMoreElements()) {
@@ -2370,7 +2375,7 @@ function BrowserOnAboutPageLoad(doc) {
/**
* Handle command events bubbling up from error page content
*/
-let BrowserOnClick = {
+var BrowserOnClick = {
handleEvent: function BrowserOnClick_handleEvent(aEvent) {
if (!aEvent.isTrusted || // Don't trust synthetic events
aEvent.button == 2 || aEvent.target.localName != "button") {
@@ -7096,7 +7101,8 @@ function focusNextFrame(event) {
if (element.ownerDocument == document)
focusAndSelectUrlBar();
}
-let BrowserChromeTest = {
+
+var BrowserChromeTest = {
_cb: null,
_ready: false,
markAsReady: function () {
@@ -7114,7 +7120,7 @@ let BrowserChromeTest = {
}
};
-let ToolbarIconColor = {
+var ToolbarIconColor = {
init: function () {
this._initialized = true;
diff --git a/application/palemoon/base/content/browser.xul b/application/palemoon/base/content/browser.xul
index df152bbaa..ea9c3f969 100644
--- a/application/palemoon/base/content/browser.xul
+++ b/application/palemoon/base/content/browser.xul
@@ -132,6 +132,20 @@
<!-- for url bar autocomplete -->
<panel type="autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true"/>
+ <!-- for date/time picker. consumeoutsideclicks is set to never, so that
+ clicks on the anchored input box are never consumed. -->
+ <panel id="DateTimePickerPanel"
+ type="arrow"
+ hidden="true"
+ orient="vertical"
+ noautofocus="true"
+ norolluponanchor="true"
+ consumeoutsideclicks="never"
+ level="parent"
+ tabspecific="true">
+ <iframe id="dateTimePopupFrame"/>
+ </panel>
+
<!-- for invalid form error message -->
<panel id="invalid-form-popup" type="arrow" orient="vertical" noautofocus="true" hidden="true" level="parent">
<description/>
@@ -139,7 +153,6 @@
<panel id="editBookmarkPanel"
type="arrow"
- footertype="promobox"
orient="vertical"
ignorekeys="true"
consumeoutsideclicks="true"
@@ -946,7 +959,8 @@
flex="1" contenttooltip="aHTMLTooltip"
tabcontainer="tabbrowser-tabs"
contentcontextmenu="contentAreaContextMenu"
- autocompletepopup="PopupAutoComplete"/>
+ autocompletepopup="PopupAutoComplete"
+ datetimepicker="DateTimePickerPanel"/>
<chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/>
<statuspanel id="statusbar-display" inactive="true"/>
</vbox>
diff --git a/application/palemoon/base/content/newtab/drag.js b/application/palemoon/base/content/newtab/drag.js
index 8f0bf674e..fbd688faa 100644
--- a/application/palemoon/base/content/newtab/drag.js
+++ b/application/palemoon/base/content/newtab/drag.js
@@ -7,7 +7,7 @@
/**
* This singleton implements site dragging functionality.
*/
-let gDrag = {
+var gDrag = {
/**
* The site offset to the drag start point.
*/
diff --git a/application/palemoon/base/content/newtab/dragDataHelper.js b/application/palemoon/base/content/newtab/dragDataHelper.js
index a66e4e87e..54348ab14 100644
--- a/application/palemoon/base/content/newtab/dragDataHelper.js
+++ b/application/palemoon/base/content/newtab/dragDataHelper.js
@@ -4,7 +4,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#endif
-let gDragDataHelper = {
+var gDragDataHelper = {
get mimeType() {
return "text/x-moz-url";
},
diff --git a/application/palemoon/base/content/newtab/drop.js b/application/palemoon/base/content/newtab/drop.js
index d7bf30506..748652455 100644
--- a/application/palemoon/base/content/newtab/drop.js
+++ b/application/palemoon/base/content/newtab/drop.js
@@ -11,7 +11,7 @@ const DELAY_REARRANGE_MS = 100;
/**
* This singleton implements site dropping functionality.
*/
-let gDrop = {
+var gDrop = {
/**
* The last drop target.
*/
diff --git a/application/palemoon/base/content/newtab/dropPreview.js b/application/palemoon/base/content/newtab/dropPreview.js
index 903762345..fd7587a35 100644
--- a/application/palemoon/base/content/newtab/dropPreview.js
+++ b/application/palemoon/base/content/newtab/dropPreview.js
@@ -9,7 +9,7 @@
* indicate the transformation that results from dropping a cell at a certain
* position.
*/
-let gDropPreview = {
+var gDropPreview = {
/**
* Rearranges the sites currently contained in the grid when a site would be
* dropped onto the given cell.
diff --git a/application/palemoon/base/content/newtab/dropTargetShim.js b/application/palemoon/base/content/newtab/dropTargetShim.js
index a85a6ccd6..046dbea1e 100644
--- a/application/palemoon/base/content/newtab/dropTargetShim.js
+++ b/application/palemoon/base/content/newtab/dropTargetShim.js
@@ -9,7 +9,7 @@
* the default DnD target detection relies on the cursor's position. We want
* to pick a drop target based on the dragged site's position.
*/
-let gDropTargetShim = {
+var gDropTargetShim = {
/**
* Cache for the position of all cells, cleaned after drag finished.
*/
diff --git a/application/palemoon/base/content/newtab/grid.js b/application/palemoon/base/content/newtab/grid.js
index 3b7dfc35f..46e0b804b 100644
--- a/application/palemoon/base/content/newtab/grid.js
+++ b/application/palemoon/base/content/newtab/grid.js
@@ -7,7 +7,7 @@
/**
* This singleton represents the grid that contains all sites.
*/
-let gGrid = {
+var gGrid = {
/**
* The DOM node of the grid.
*/
diff --git a/application/palemoon/base/content/newtab/newTab.js b/application/palemoon/base/content/newtab/newTab.js
index 6d8647ab6..bba73fd7a 100644
--- a/application/palemoon/base/content/newtab/newTab.js
+++ b/application/palemoon/base/content/newtab/newTab.js
@@ -18,7 +18,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Rect",
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
-let {
+var {
links: gLinks,
allPages: gAllPages,
linkChecker: gLinkChecker,
diff --git a/application/palemoon/base/content/newtab/page.js b/application/palemoon/base/content/newtab/page.js
index afe5bfba8..fc836a55e 100644
--- a/application/palemoon/base/content/newtab/page.js
+++ b/application/palemoon/base/content/newtab/page.js
@@ -8,7 +8,7 @@
* This singleton represents the whole 'New Tab Page' and takes care of
* initializing all its components.
*/
-let gPage = {
+var gPage = {
/**
* Initializes the page.
*/
diff --git a/application/palemoon/base/content/newtab/transformations.js b/application/palemoon/base/content/newtab/transformations.js
index 6d1554f5f..0711d7d0a 100644
--- a/application/palemoon/base/content/newtab/transformations.js
+++ b/application/palemoon/base/content/newtab/transformations.js
@@ -9,7 +9,7 @@
* in the DOM and by showing or hiding the node. It additionally provides
* convenience methods to work with a site's DOM node.
*/
-let gTransformation = {
+var gTransformation = {
/**
* Returns the width of the left and top border of a cell. We need to take it
* into account when measuring and comparing site and cell positions.
diff --git a/application/palemoon/base/content/newtab/undo.js b/application/palemoon/base/content/newtab/undo.js
index 5f619e980..b856914d2 100644
--- a/application/palemoon/base/content/newtab/undo.js
+++ b/application/palemoon/base/content/newtab/undo.js
@@ -8,7 +8,7 @@
* Dialog allowing to undo the removal of single site or to completely restore
* the grid's original state.
*/
-let gUndoDialog = {
+var gUndoDialog = {
/**
* The undo dialog's timeout in miliseconds.
*/
diff --git a/application/palemoon/base/content/newtab/updater.js b/application/palemoon/base/content/newtab/updater.js
index 7b483e037..e6da37f87 100644
--- a/application/palemoon/base/content/newtab/updater.js
+++ b/application/palemoon/base/content/newtab/updater.js
@@ -8,7 +8,7 @@
* This singleton provides functionality to update the current grid to a new
* set of pinned and blocked sites. It adds, moves and removes sites.
*/
-let gUpdater = {
+var gUpdater = {
/**
* Updates the current grid according to its pinned and blocked sites.
* This removes old, moves existing and creates new sites to fill gaps.
diff --git a/application/palemoon/base/content/popup-notifications.inc b/application/palemoon/base/content/popup-notifications.inc
index 7be975b7f..31a72b489 100644
--- a/application/palemoon/base/content/popup-notifications.inc
+++ b/application/palemoon/base/content/popup-notifications.inc
@@ -2,7 +2,6 @@
<panel id="notification-popup"
type="arrow"
- footertype="promobox"
position="after_start"
hidden="true"
orient="vertical"
diff --git a/application/palemoon/base/content/sync/aboutSyncTabs.js b/application/palemoon/base/content/sync/aboutSyncTabs.js
index 535c43e56..bc624a459 100644
--- a/application/palemoon/base/content/sync/aboutSyncTabs.js
+++ b/application/palemoon/base/content/sync/aboutSyncTabs.js
@@ -11,7 +11,7 @@ Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-let RemoteTabViewer = {
+var RemoteTabViewer = {
_tabsList: null,
init: function () {
diff --git a/application/palemoon/base/content/sync/addDevice.js b/application/palemoon/base/content/sync/addDevice.js
index 556e75768..40862a791 100644
--- a/application/palemoon/base/content/sync/addDevice.js
+++ b/application/palemoon/base/content/sync/addDevice.js
@@ -15,7 +15,7 @@ const ADD_DEVICE_PAGE = 0;
const SYNC_KEY_PAGE = 1;
const DEVICE_CONNECTED_PAGE = 2;
-let gSyncAddDevice = {
+var gSyncAddDevice = {
init: function init() {
this.pin1.setAttribute("maxlength", PIN_PART_LENGTH);
diff --git a/application/palemoon/base/content/sync/genericChange.js b/application/palemoon/base/content/sync/genericChange.js
index 6d1ce9485..0c6dc145e 100644
--- a/application/palemoon/base/content/sync/genericChange.js
+++ b/application/palemoon/base/content/sync/genericChange.js
@@ -8,7 +8,7 @@ const Cc = Components.classes;
Components.utils.import("resource://services-sync/main.js");
Components.utils.import("resource://gre/modules/Services.jsm");
-let Change = {
+var Change = {
_dialog: null,
_dialogType: null,
_status: null,
diff --git a/application/palemoon/base/content/sync/progress.js b/application/palemoon/base/content/sync/progress.js
index 2063f612a..101160fa8 100644
--- a/application/palemoon/base/content/sync/progress.js
+++ b/application/palemoon/base/content/sync/progress.js
@@ -6,8 +6,8 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-sync/main.js");
-let gProgressBar;
-let gCounter = 0;
+var gProgressBar;
+var gCounter = 0;
function onLoad(event) {
Services.obs.addObserver(onEngineSync, "weave:engine:sync:finish", false);
diff --git a/application/palemoon/base/content/sync/quota.js b/application/palemoon/base/content/sync/quota.js
index 7117a2ddf..454052754 100644
--- a/application/palemoon/base/content/sync/quota.js
+++ b/application/palemoon/base/content/sync/quota.js
@@ -10,7 +10,7 @@ const Cu = Components.utils;
Cu.import("resource://services-sync/main.js");
Cu.import("resource://gre/modules/DownloadUtils.jsm");
-let gSyncQuota = {
+var gSyncQuota = {
init: function init() {
this.bundle = document.getElementById("quotaStrings");
@@ -86,7 +86,7 @@ let gSyncQuota = {
};
-let gUsageTreeView = {
+var gUsageTreeView = {
_ignored: {keys: true,
meta: true,
diff --git a/application/palemoon/base/content/sync/utils.js b/application/palemoon/base/content/sync/utils.js
index 0c02b5bc0..d41ecf18a 100644
--- a/application/palemoon/base/content/sync/utils.js
+++ b/application/palemoon/base/content/sync/utils.js
@@ -8,7 +8,7 @@
const PERMISSIONS_RWUSR = 0x180;
// Weave should always exist before before this file gets included.
-let gSyncUtils = {
+var gSyncUtils = {
get bundle() {
delete this.bundle;
return this.bundle = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
diff --git a/application/palemoon/base/content/tabbrowser.xml b/application/palemoon/base/content/tabbrowser.xml
index 69d824fd5..49db93377 100644
--- a/application/palemoon/base/content/tabbrowser.xml
+++ b/application/palemoon/base/content/tabbrowser.xml
@@ -30,7 +30,7 @@
<xul:vbox flex="1" class="browserContainer">
<xul:stack flex="1" class="browserStack" anonid="browserStack">
<xul:browser anonid="initialBrowser" type="content-primary" message="true" disablehistory="true"
- xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
+ xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,datetimepicker"/>
</xul:stack>
</xul:vbox>
</xul:hbox>
@@ -138,19 +138,19 @@
]]></getter>
</property>
- <property name="formValidationAnchor" readonly="true">
+ <property name="popupAnchor" readonly="true">
<getter><![CDATA[
- if (this.mCurrentTab._formValidationAnchor) {
- return this.mCurrentTab._formValidationAnchor;
+ if (this.mCurrentTab._popupAnchor) {
+ return this.mCurrentTab._popupAnchor;
}
let stack = this.mCurrentBrowser.parentNode;
- // Create an anchor for the form validation popup
+ // Create an anchor for the popup
const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
- let formValidationAnchor = document.createElementNS(NS_XUL, "hbox");
- formValidationAnchor.className = "form-validation-anchor";
- formValidationAnchor.hidden = true;
- stack.appendChild(formValidationAnchor);
- return this.mCurrentTab._formValidationAnchor = formValidationAnchor;
+ let popupAnchor = document.createElementNS(NS_XUL, "hbox");
+ popupAnchor.className = "popup-anchor";
+ popupAnchor.hidden = true;
+ stack.appendChild(popupAnchor);
+ return this.mCurrentTab._popupAnchor = popupAnchor;
]]></getter>
</property>
@@ -1502,6 +1502,10 @@
b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
+ if (this.hasAttribute("datetimepicker")) {
+ b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
+ }
+
// Create the browserStack container
var stack = document.createElementNS(NS_XUL, "stack");
stack.className = "browserStack";
diff --git a/application/palemoon/base/content/urlbarBindings.xml b/application/palemoon/base/content/urlbarBindings.xml
index bdb7a6a12..07cd5380d 100644
--- a/application/palemoon/base/content/urlbarBindings.xml
+++ b/application/palemoon/base/content/urlbarBindings.xml
@@ -1778,195 +1778,6 @@
</implementation>
</binding>
- <binding id="promobox">
- <content>
- <xul:hbox class="panel-promo-box" align="start" flex="1">
- <xul:hbox align="center" flex="1">
- <xul:image class="panel-promo-icon"/>
- <xul:description anonid="promo-message" class="panel-promo-message" flex="1">
- <xul:description anonid="promo-link"
- class="plain text-link inline-link"
- onclick="document.getBindingParent(this).onLinkClick();"/>
- </xul:description>
- </xul:hbox>
- <xul:toolbarbutton class="panel-promo-closebutton close-icon"
- oncommand="document.getBindingParent(this).onCloseButtonCommand();"
- tooltiptext="&closeNotification.tooltip;"/>
- </xul:hbox>
- </content>
-
- <implementation implements="nsIDOMEventListener">
- <constructor><![CDATA[
- this._panel.addEventListener("popupshowing", this, false);
- ]]></constructor>
-
- <destructor><![CDATA[
- this._panel.removeEventListener("popupshowing", this, false);
- ]]></destructor>
-
- <field name="_panel" readonly="true"><![CDATA[
- let node = this.parentNode;
- while(node && node.localName != "panel") {
- node = node.parentNode;
- }
- node;
- ]]></field>
- <field name="_promomessage" readonly="true">
- document.getAnonymousElementByAttribute(this, "anonid", "promo-message");
- </field>
- <field name="_promolink" readonly="true">
- document.getAnonymousElementByAttribute(this, "anonid", "promo-link");
- </field>
- <field name="_brandBundle" readonly="true">
- Services.strings.createBundle("chrome://branding/locale/brand.properties");
- </field>
- <property name="_viewsLeftMap">
- <getter><![CDATA[
- let viewsLeftMap = {};
- try {
- viewsLeftMap = JSON.parse(Services.prefs.getCharPref("browser.syncPromoViewsLeftMap"));
- } catch (ex) {
- // If the old preference exists, migrate it to the new one.
- try {
- let oldPref = Services.prefs.getIntPref("browser.syncPromoViewsLeft");
- Services.prefs.clearUserPref("browser.syncPromoViewsLeft");
- viewsLeftMap.bookmarks = oldPref;
- viewsLeftMap.passwords = oldPref;
- Services.prefs.setCharPref("browser.syncPromoViewsLeftMap",
- JSON.stringify(viewsLeftMap));
- } catch (ex2) {}
- }
- return viewsLeftMap;
- ]]></getter>
- </property>
- <property name="_viewsLeft">
- <getter><![CDATA[
- let views = 5;
- let map = this._viewsLeftMap;
- if (this._notificationType in map) {
- views = map[this._notificationType];
- }
- return views;
- ]]></getter>
- <setter><![CDATA[
- let map = this._viewsLeftMap;
- map[this._notificationType] = val;
- Services.prefs.setCharPref("browser.syncPromoViewsLeftMap",
- JSON.stringify(map));
- return val;
- ]]></setter>
- </property>
- <property name="_notificationType">
- <getter><![CDATA[
- // Use the popupid attribute to identify the notification type,
- // otherwise just rely on the panel id for common arrowpanels.
- let type = this._panel.firstChild.getAttribute("popupid") ||
- this._panel.id;
- if (type.startsWith("password-"))
- return "passwords";
- if (type == "editBookmarkPanel")
- return "bookmarks";
- if (type == "addon-install-complete") {
- if (!Services.prefs.prefHasUserValue("services.sync.username"))
- return "addons";
- if (!Services.prefs.getBoolPref("services.sync.engine.addons"))
- return "addons-sync-disabled";
- }
- return null;
- ]]></getter>
- </property>
- <property name="_notificationMessage">
- <getter><![CDATA[
- return gNavigatorBundle.getFormattedString(
- "syncPromoNotification." + this._notificationType + ".description",
- [this._brandBundle.GetStringFromName("syncBrandShortName")]
- );
- ]]></getter>
- </property>
- <property name="_notificationLink">
- <getter><![CDATA[
- if (this._notificationType == "addons-sync-disabled") {
- return "https://forum.palemoon.org/viewforum.php?f=52";
- }
- return "http://www.palemoon.org/sync/";
- ]]></getter>
- </property>
- <method name="onCloseButtonCommand">
- <body><![CDATA[
- this._viewsLeft = 0;
- this.hidden = true;
- ]]></body>
- </method>
- <method name="onLinkClick">
- <body><![CDATA[
- // Open a new selected tab and close the current panel.
- gBrowser.loadOneTab(this._promolink.getAttribute("href"),
- { inBackground: false });
- this._panel.hidePopup();
- ]]></body>
- </method>
- <method name="handleEvent">
- <parameter name="event"/>
- <body><![CDATA[
- if (event.type != "popupshowing" || event.target != this._panel)
- return;
-
- // A previous notification may have unhidden this.
- this.hidden = true;
-
- // Only handle supported notification panels.
- if (!this._notificationType) {
- return;
- }
-
- let viewsLeft = this._viewsLeft;
- if (viewsLeft) {
- // XXX: Short-circuit this for now.
- // TO-DO: Clean up this code for sync promotion
- this._viewsLeft = 0;
- viewsLeft = 0;
- return;
- // XXX
-
- if (Services.prefs.prefHasUserValue("services.sync.username") &&
- this._notificationType != "addons-sync-disabled") {
- // If the user has already setup Sync, don't show the notification.
- this._viewsLeft = 0;
- // Be sure to hide the panel, in case it was visible and the user
- // decided to setup Sync after noticing it.
- viewsLeft = 0;
- // The panel is still hidden, just bail out.
- return;
- }
- else {
- this._viewsLeft = viewsLeft - 1;
- }
-
- this._promolink.setAttribute("href", this._notificationLink);
- this._promolink.value = gNavigatorBundle.getString("syncPromoNotification.learnMoreLinkText");
-
- this.hidden = false;
-
- // HACK: The description element doesn't wrap correctly in panels,
- // thus set a width on it, based on the available space, before
- // setting its textContent. Then set its height as well, to
- // fix wrong height calculation on Linux (bug 659578).
- this._panel.addEventListener("popupshown", function panelShown() {
- this._panel.removeEventListener("popupshown", panelShown, true);
- // Previous popupShown events may close the panel or change
- // its contents, so ensure this is still valid.
- if (this._panel.state != "open" || !this._notificationType)
- return;
- this._promomessage.width = this._promomessage.getBoundingClientRect().width;
- this._promomessage.firstChild.textContent = this._notificationMessage;
- this._promomessage.height = this._promomessage.getBoundingClientRect().height;
- }.bind(this), true);
- }
- ]]></body>
- </method>
- </implementation>
- </binding>
-
<binding id="toolbarbutton-badged" display="xul:button"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
<content>
diff --git a/application/palemoon/branding/official/pref/palemoon-branding.js b/application/palemoon/branding/official/pref/palemoon-branding.js
index 4f5d196d7..8e8703fb7 100644
--- a/application/palemoon/branding/official/pref/palemoon-branding.js
+++ b/application/palemoon/branding/official/pref/palemoon-branding.js
@@ -8,6 +8,8 @@ pref("app.releaseNotesURL", "http://www.palemoon.org/releasenotes.shtml");
// Enable Firefox compatmode by default.
pref("general.useragent.compatMode", 2);
+pref("general.useragent.compatMode.gecko", true);
+pref("general.useragent.compatMode.firefox", true);
// ========================= updates ========================
#if defined(XP_WIN)
diff --git a/application/palemoon/branding/unstable/pref/palemoon-branding.js b/application/palemoon/branding/unstable/pref/palemoon-branding.js
index 4c088f76d..dda6f86a5 100644
--- a/application/palemoon/branding/unstable/pref/palemoon-branding.js
+++ b/application/palemoon/branding/unstable/pref/palemoon-branding.js
@@ -8,6 +8,8 @@ pref("app.releaseNotesURL", "http://www.palemoon.org/unstable/releasenotes.shtml
// Enable Firefox compatmode by default.
pref("general.useragent.compatMode", 2);
+pref("general.useragent.compatMode.gecko", true);
+pref("general.useragent.compatMode.firefox", true);
// ========================= updates ========================
#if defined(XP_WIN)
diff --git a/application/palemoon/components/downloads/DownloadsCommon.jsm b/application/palemoon/components/downloads/DownloadsCommon.jsm
index 0921f8400..c6614e780 100644
--- a/application/palemoon/components/downloads/DownloadsCommon.jsm
+++ b/application/palemoon/components/downloads/DownloadsCommon.jsm
@@ -104,7 +104,7 @@ const kPartialDownloadSuffix = ".part";
const kPrefBranch = Services.prefs.getBranch("browser.download.");
-let PrefObserver = {
+var PrefObserver = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
getPref: function PO_getPref(name) {
diff --git a/application/palemoon/components/downloads/content/contentAreaDownloadsView.js b/application/palemoon/components/downloads/content/contentAreaDownloadsView.js
index 29e2e368c..fbb18ab04 100644
--- a/application/palemoon/components/downloads/content/contentAreaDownloadsView.js
+++ b/application/palemoon/components/downloads/content/contentAreaDownloadsView.js
@@ -4,7 +4,7 @@
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-let ContentAreaDownloadsView = {
+var ContentAreaDownloadsView = {
init: function CADV_init() {
let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"));
// Do not display the Places downloads in private windows
diff --git a/application/palemoon/components/nsBrowserGlue.js b/application/palemoon/components/nsBrowserGlue.js
index cdb0b7522..c4205c2c5 100644
--- a/application/palemoon/components/nsBrowserGlue.js
+++ b/application/palemoon/components/nsBrowserGlue.js
@@ -35,6 +35,7 @@ Cu.import("resource://gre/modules/Services.jsm");
["OS", "resource://gre/modules/osfile.jsm"],
["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
+ ["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"],
].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
@@ -167,6 +168,7 @@ BrowserGlue.prototype = {
} catch (e) {
Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
}
+ DateTimePickerHelper.uninit();
break;
#ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS
case "browser-lastwindow-close-requested":
@@ -496,6 +498,8 @@ BrowserGlue.prototype = {
}
#endif
+ DateTimePickerHelper.init();
+
this._trackSlowStartup();
},
diff --git a/application/palemoon/components/places/BrowserPlaces.manifest b/application/palemoon/components/places/BrowserPlaces.manifest
deleted file mode 100644
index 3b25f50f4..000000000
--- a/application/palemoon/components/places/BrowserPlaces.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {6bcb9bde-9018-4443-a071-c32653469597} PlacesProtocolHandler.js
-contract @mozilla.org/network/protocol;1?name=place {6bcb9bde-9018-4443-a071-c32653469597}
diff --git a/application/palemoon/components/places/PlacesProtocolHandler.js b/application/palemoon/components/places/PlacesProtocolHandler.js
deleted file mode 100644
index ebffd2e28..000000000
--- a/application/palemoon/components/places/PlacesProtocolHandler.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * vim: sw=2 ts=2 sts=2 et
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-Components.utils.import("resource://gre/modules/NetUtil.jsm");
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-const SCHEME = "place";
-const URL = "chrome://browser/content/places/content-ui/controller.xhtml";
-
-function PlacesProtocolHandler() {}
-
-PlacesProtocolHandler.prototype = {
- scheme: SCHEME,
- defaultPort: -1,
- protocolFlags: Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
- Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
- Ci.nsIProtocolHandler.URI_NORELATIVE |
- Ci.nsIProtocolHandler.URI_NOAUTH,
-
- newURI: function PPH_newURI(aSpec, aOriginCharset, aBaseUri) {
- let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
- uri.spec = aSpec;
- return uri;
- },
-
- newChannel: function PPH_newChannel(aUri) {
- let chan = NetUtil.newChannel(URL);
- chan.originalURI = aUri;
- return chan;
- },
-
- allowPort: function PPH_allowPort(aPort, aScheme) {
- return false;
- },
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIProtocolHandler
- ]),
-
- classID: Components.ID("{6bcb9bde-9018-4443-a071-c32653469597}")
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PlacesProtocolHandler]);
diff --git a/application/palemoon/components/places/content/controller.js b/application/palemoon/components/places/content/controller.js
index 4d3773905..e2ae2afb0 100644
--- a/application/palemoon/components/places/content/controller.js
+++ b/application/palemoon/components/places/content/controller.js
@@ -1603,7 +1603,7 @@ PlacesController.prototype = {
* the view that the item(s) have been dropped on was not necessarily active.
* Drop functions are passed the view that is being dropped on.
*/
-let PlacesControllerDragHelper = {
+var PlacesControllerDragHelper = {
/**
* DOM Element currently being dragged over
*/
diff --git a/application/palemoon/components/places/content/places.js b/application/palemoon/components/places/content/places.js
index 9ecdfebe0..a94193823 100644
--- a/application/palemoon/components/places/content/places.js
+++ b/application/palemoon/components/places/content/places.js
@@ -1377,7 +1377,7 @@ var ViewMenu = {
}
}
-let ContentArea = {
+var ContentArea = {
_specialViews: new Map(),
init: function CA_init() {
@@ -1511,7 +1511,7 @@ let ContentArea = {
}
};
-let ContentTree = {
+var ContentTree = {
init: function CT_init() {
this._view = document.getElementById("placeContent");
},
diff --git a/application/palemoon/components/places/moz.build b/application/palemoon/components/places/moz.build
index fc69beed9..2e35e1951 100644
--- a/application/palemoon/components/places/moz.build
+++ b/application/palemoon/components/places/moz.build
@@ -6,9 +6,5 @@
JAR_MANIFESTS += ['jar.mn']
-EXTRA_COMPONENTS += [
- 'BrowserPlaces.manifest',
- 'PlacesProtocolHandler.js',
-]
EXTRA_JS_MODULES += [ 'PlacesUIUtils.jsm' ]
diff --git a/application/palemoon/components/preferences/aboutPermissions.js b/application/palemoon/components/preferences/aboutPermissions.js
index 106d45f89..31b48f88e 100644
--- a/application/palemoon/components/preferences/aboutPermissions.js
+++ b/application/palemoon/components/preferences/aboutPermissions.js
@@ -13,15 +13,15 @@ Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
-let gFaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
+var gFaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
getService(Ci.nsIFaviconService);
-let gPlacesDatabase = Cc["@mozilla.org/browser/nav-history-service;1"].
+var gPlacesDatabase = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsPIPlacesDatabase).
DBConnection.
clone(true);
-let gSitesStmt = gPlacesDatabase.createAsyncStatement(
+var gSitesStmt = gPlacesDatabase.createAsyncStatement(
"SELECT get_unreversed_host(rev_host) AS host " +
"FROM moz_places " +
"WHERE rev_host > '.' " +
@@ -30,12 +30,12 @@ let gSitesStmt = gPlacesDatabase.createAsyncStatement(
"ORDER BY MAX(frecency) DESC " +
"LIMIT :limit");
-let gVisitStmt = gPlacesDatabase.createAsyncStatement(
+var gVisitStmt = gPlacesDatabase.createAsyncStatement(
"SELECT SUM(visit_count) AS count " +
"FROM moz_places " +
"WHERE rev_host = :rev_host");
-let gFlash = {
+var gFlash = {
name: "Shockwave Flash",
betterName: "Adobe Flash",
type: "application/x-shockwave-flash",
@@ -290,7 +290,7 @@ Site.prototype = {
*
* Inspired by pageinfo/permissions.js
*/
-let PermissionDefaults = {
+var PermissionDefaults = {
UNKNOWN: Ci.nsIPermissionManager.UNKNOWN_ACTION, // 0
ALLOW: Ci.nsIPermissionManager.ALLOW_ACTION, // 1
DENY: Ci.nsIPermissionManager.DENY_ACTION, // 2
@@ -449,7 +449,7 @@ let PermissionDefaults = {
/**
* AboutPermissions manages the about:permissions page.
*/
-let AboutPermissions = {
+var AboutPermissions = {
/**
* Maximum number of sites to return from the places database.
*/
diff --git a/application/palemoon/components/preferences/sanitize.js b/application/palemoon/components/preferences/sanitize.js
index 893155e62..15e6f58f4 100644
--- a/application/palemoon/components/preferences/sanitize.js
+++ b/application/palemoon/components/preferences/sanitize.js
@@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-let gSanitizeDialog = Object.freeze({
+var gSanitizeDialog = Object.freeze({
onClearHistoryChanged: function () {
let downloadsPref = document.getElementById("privacy.clearOnShutdown.downloads");
let historyPref = document.getElementById("privacy.clearOnShutdown.history");
diff --git a/application/palemoon/components/preferences/sync.js b/application/palemoon/components/preferences/sync.js
index 9c7f1aa1f..f29728dbf 100644
--- a/application/palemoon/components/preferences/sync.js
+++ b/application/palemoon/components/preferences/sync.js
@@ -9,7 +9,7 @@ const PAGE_NO_ACCOUNT = 0;
const PAGE_HAS_ACCOUNT = 1;
const PAGE_NEEDS_UPDATE = 2;
-let gSyncPane = {
+var gSyncPane = {
_stringBundle: null,
prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
"engine.tabs", "engine.history"],
diff --git a/application/palemoon/components/sessionstore/SessionStorage.jsm b/application/palemoon/components/sessionstore/SessionStorage.jsm
index 192352c49..64aef35a5 100644
--- a/application/palemoon/components/sessionstore/SessionStorage.jsm
+++ b/application/palemoon/components/sessionstore/SessionStorage.jsm
@@ -38,7 +38,7 @@ this.SessionStorage = {
Object.freeze(SessionStorage);
-let DomStorage = {
+var DomStorage = {
/**
* Reads all session storage data from the given docShell.
* @param aDocShell
@@ -142,7 +142,7 @@ let DomStorage = {
}
};
-let History = {
+var History = {
/**
* Returns a given history entry's URI.
* @param aHistory
diff --git a/application/palemoon/components/sessionstore/SessionStore.jsm b/application/palemoon/components/sessionstore/SessionStore.jsm
index a8e7adfcc..f7c495be8 100644
--- a/application/palemoon/components/sessionstore/SessionStore.jsm
+++ b/application/palemoon/components/sessionstore/SessionStore.jsm
@@ -88,7 +88,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
// retrieved from a given docShell if not already collected before.
// This is made so they're automatically in sync with all nsIDocShell.allow*
// properties.
-let gDocShellCapabilities = (function () {
+var gDocShellCapabilities = (function () {
let caps;
return docShell => {
@@ -247,7 +247,7 @@ this.SessionStore = {
// Freeze the SessionStore object. We don't want anyone to modify it.
Object.freeze(SessionStore);
-let SessionStoreInternal = {
+var SessionStoreInternal = {
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIDOMEventListener,
Ci.nsIObserver,
@@ -4471,7 +4471,7 @@ let SessionStoreInternal = {
* pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
* restored with restore_hidden_tabs=true.
*/
-let TabRestoreQueue = {
+var TabRestoreQueue = {
// The separate buckets used to store tabs.
tabs: {priority: [], visible: [], hidden: []},
@@ -4608,7 +4608,7 @@ let TabRestoreQueue = {
// A map storing a closed window's state data until it goes aways (is GC'ed).
// This ensures that API clients can still read (but not write) states of
// windows they still hold a reference to but we don't.
-let DyingWindowCache = {
+var DyingWindowCache = {
_data: new WeakMap(),
has: function (window) {
@@ -4631,7 +4631,7 @@ let DyingWindowCache = {
// A set of tab attributes to persist. We will read a given list of tab
// attributes when collecting tab data and will re-set those attributes when
// the given tab data is restored to a new tab.
-let TabAttributes = {
+var TabAttributes = {
_attrs: new Set(),
// We never want to directly read or write those attributes.
@@ -4677,7 +4677,7 @@ let TabAttributes = {
// This is used to help meter the number of restoring tabs. This is the control
// point for telling the next tab to restore. It gets attached to each gBrowser
// via gBrowser.addTabsProgressListener
-let gRestoreTabsProgressListener = {
+var gRestoreTabsProgressListener = {
onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
// Ignore state changes on browsers that we've already restored and state
// changes that aren't applicable.
diff --git a/application/palemoon/components/sessionstore/_SessionFile.jsm b/application/palemoon/components/sessionstore/_SessionFile.jsm
index 9d40b9cca..62b4d1687 100644
--- a/application/palemoon/components/sessionstore/_SessionFile.jsm
+++ b/application/palemoon/components/sessionstore/_SessionFile.jsm
@@ -130,7 +130,7 @@ const TaskUtils = {
}
};
-let SessionFileInternal = {
+var SessionFileInternal = {
/**
* A promise fulfilled once initialization is complete
*/
diff --git a/application/palemoon/components/sessionstore/nsSessionStartup.js b/application/palemoon/components/sessionstore/nsSessionStartup.js
index 7f07e9050..04037c10e 100644
--- a/application/palemoon/components/sessionstore/nsSessionStartup.js
+++ b/application/palemoon/components/sessionstore/nsSessionStartup.js
@@ -50,7 +50,7 @@ function debug(aMsg) {
Services.console.logStringMessage(aMsg);
}
-let gOnceInitializedDeferred = Promise.defer();
+var gOnceInitializedDeferred = Promise.defer();
/* :::::::: The Service ::::::::::::::: */
diff --git a/application/palemoon/components/shell/ShellService.jsm b/application/palemoon/components/shell/ShellService.jsm
index 12e275bdb..74632b692 100644
--- a/application/palemoon/components/shell/ShellService.jsm
+++ b/application/palemoon/components/shell/ShellService.jsm
@@ -17,7 +17,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
/**
* Internal functionality to save and restore the docShell.allow* properties.
*/
-let ShellServiceInternal = {
+var ShellServiceInternal = {
/**
* Used to determine whether or not to offer "Set as desktop background"
* functionality. Even if shell service is available it is not
diff --git a/application/palemoon/installer/Makefile.in b/application/palemoon/installer/Makefile.in
index 7cd77bdca..368b16efc 100644
--- a/application/palemoon/installer/Makefile.in
+++ b/application/palemoon/installer/Makefile.in
@@ -34,9 +34,9 @@ ifdef MOZ_ENABLE_GNOME_COMPONENT
DEFINES += -DMOZ_ENABLE_GNOME_COMPONENT=1
endif
-ifdef MOZ_WIDGET_GTK
+ifneq (,$(filter gtk%,$(MOZ_WIDGET_TOOLKIT)))
DEFINES += -DMOZ_GTK=1
-ifdef MOZ_ENABLE_GTK3
+ifeq ($(MOZ_WIDGET_TOOLKIT),gtk3)
DEFINES += -DMOZ_GTK3=1
endif
endif
@@ -131,16 +131,17 @@ AB = $(firstword $(subst -, ,$(AB_CD)))
DEFINES += -DAB=$(AB)
DEFINES += -DMOZ_ICU_VERSION=$(MOZ_ICU_VERSION)
-ifdef MOZ_NATIVE_ICU
-DEFINES += -DMOZ_NATIVE_ICU
+ifdef MOZ_SYSTEM_ICU
+DEFINES += -DMOZ_SYSTEM_ICU
endif
-ifdef MOZ_SHARED_ICU
-DEFINES += -DMOZ_SHARED_ICU
+ifdef MOZ_ICU_DATA_ARCHIVE
+DEFINES += -DMOZ_ICU_DATA_ARCHIVE
endif
ifdef MOZ_JEMALLOC3
DEFINES += -DMOZ_JEMALLOC3
endif
DEFINES += -DMOZ_ICU_DBG_SUFFIX=$(MOZ_ICU_DBG_SUFFIX)
+DEFINES += -DICU_DATA_FILE=$(ICU_DATA_FILE)
ifdef CLANG_CXX
DEFINES += -DCLANG_CXX
endif
diff --git a/application/palemoon/locales/en-US/chrome/browser/browser.properties b/application/palemoon/locales/en-US/chrome/browser/browser.properties
index 5e08c3e50..518f1b0de 100644
--- a/application/palemoon/locales/en-US/chrome/browser/browser.properties
+++ b/application/palemoon/locales/en-US/chrome/browser/browser.properties
@@ -364,26 +364,6 @@ restartButton=Restart
# menu, set this to "true". Otherwise, you can leave it as "false".
browser.menu.showCharacterEncoding=false
-# LOCALIZATION NOTE (syncPromoNotification.bookmarks.label): This appears in
-# the add bookmark star panel. %S will be replaced by syncBrandShortName.
-# The final space separates this text from the Learn More link.
-syncPromoNotification.bookmarks.description=You can access your bookmarks on all your devices with %S.\u0020
-# LOCALIZATION NOTE (syncPromoNotification.passwords.label): This appears in
-# the remember password panel. %S will be replaced by syncBrandShortName.
-# The final space separates this text from the Learn More link.
-syncPromoNotification.passwords.description=You can access your passwords on all your devices with %S.\u0020
-syncPromoNotification.learnMoreLinkText=Learn More
-# LOCALIZATION NOTE (syncPromoNotification.addons.label): This appears in
-# the add-on install complete panel when Sync isn't set.
-# %S will be replaced by syncBrandShortName.
-# The final space separates this text from the Learn More link.
-syncPromoNotification.addons.description=You can access your add-ons on all your devices with %S.\u0020
-# LOCALIZATION NOTE (syncPromoNotification.addons-sync-disabled.label):
-# This appears in the add-on install complete panel when Sync is set
-# but addons sync is not. %S will be replaced by syncBrandShortName.
-# The final space separates this text from the Learn More link.
-syncPromoNotification.addons-sync-disabled.description=You can use your %S account to synchronize add-ons across multiple devices.\u0020
-
# Mozilla data reporting notification (Telemetry, Firefox Health Report, etc)
dataReportingNotification.message = %1$S automatically sends some data to %2$S so that we can improve your experience.
dataReportingNotification.button.label = Choose What I Share
diff --git a/application/palemoon/locales/en-US/chrome/overrides/netError.dtd b/application/palemoon/locales/en-US/chrome/overrides/netError.dtd
index 51aedb275..c97bd1b59 100644
--- a/application/palemoon/locales/en-US/chrome/overrides/netError.dtd
+++ b/application/palemoon/locales/en-US/chrome/overrides/netError.dtd
@@ -51,7 +51,6 @@
<p>&brandShortName; can’t load this page for some reason.</p>
">
-<!ENTITY malformedURI.title "The address isn't valid">
<!ENTITY captivePortal.title "Login to network">
<!ENTITY captivePortal.longDesc "
<p>This network may require you to login to access the internet.</p>
@@ -59,7 +58,7 @@
<!ENTITY openPortalLoginPage.label "Open Login Page">
-<!ENTITY malformedURI.title "The address isn’t valid">
+<!ENTITY malformedURI.title "The address isn't valid">
<!ENTITY malformedURI.longDesc "
<ul>
<li>Web addresses are usually written like
diff --git a/application/palemoon/modules/BrowserNewTabPreloader.jsm b/application/palemoon/modules/BrowserNewTabPreloader.jsm
index 719a9e05e..778698fba 100644
--- a/application/palemoon/modules/BrowserNewTabPreloader.jsm
+++ b/application/palemoon/modules/BrowserNewTabPreloader.jsm
@@ -79,7 +79,7 @@ this.BrowserNewTabPreloader = {
Object.freeze(BrowserNewTabPreloader);
-let Initializer = {
+var Initializer = {
_timer: null,
_observing: false,
@@ -120,7 +120,7 @@ let Initializer = {
}
};
-let Preferences = {
+var Preferences = {
_enabled: null,
_branch: null,
@@ -157,7 +157,7 @@ let Preferences = {
},
};
-let HiddenBrowsers = {
+var HiddenBrowsers = {
_browsers: null,
_updateTimer: null,
@@ -379,7 +379,7 @@ HiddenBrowser.prototype = {
}
};
-let HostFrame = {
+var HostFrame = {
_frame: null,
_deferred: null,
diff --git a/application/palemoon/modules/FormSubmitObserver.jsm b/application/palemoon/modules/FormSubmitObserver.jsm
index 72c5ca601..e4d3e765e 100644
--- a/application/palemoon/modules/FormSubmitObserver.jsm
+++ b/application/palemoon/modules/FormSubmitObserver.jsm
@@ -9,14 +9,14 @@
"use strict";
-let Cc = Components.classes;
-let Ci = Components.interfaces;
-let Cu = Components.utils;
-
-let HTMLInputElement = Ci.nsIDOMHTMLInputElement;
-let HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
-let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
-let HTMLButtonElement = Ci.nsIDOMHTMLButtonElement;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+var HTMLInputElement = Ci.nsIDOMHTMLInputElement;
+var HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
+var HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
+var HTMLButtonElement = Ci.nsIDOMHTMLButtonElement;
this.EXPORTED_SYMBOLS = [ "FormSubmitObserver" ];
diff --git a/application/palemoon/modules/FormValidationHandler.jsm b/application/palemoon/modules/FormValidationHandler.jsm
index 05be510e1..387c221d7 100644
--- a/application/palemoon/modules/FormValidationHandler.jsm
+++ b/application/palemoon/modules/FormValidationHandler.jsm
@@ -8,15 +8,15 @@
"use strict";
-let Cc = Components.classes;
-let Ci = Components.interfaces;
-let Cu = Components.utils;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "FormValidationHandler" ];
Cu.import("resource://gre/modules/Services.jsm");
-let FormValidationHandler =
+var FormValidationHandler =
{
_panel: null,
_anchor: null,
@@ -122,7 +122,7 @@ let FormValidationHandler =
this._panel.hidden = false;
let tabBrowser = aWindow.gBrowser;
- this._anchor = tabBrowser.formValidationAnchor;
+ this._anchor = tabBrowser.popupAnchor;
this._anchor.left = aPanelData.contentRect.left;
this._anchor.top = aPanelData.contentRect.top;
this._anchor.width = aPanelData.contentRect.width;
diff --git a/application/palemoon/modules/NetworkPrioritizer.jsm b/application/palemoon/modules/NetworkPrioritizer.jsm
index ea4a87790..23d688a30 100644
--- a/application/palemoon/modules/NetworkPrioritizer.jsm
+++ b/application/palemoon/modules/NetworkPrioritizer.jsm
@@ -34,8 +34,8 @@ const PRIORITY_DELTA = -10;
// Variables
-let _lastFocusedWindow = null;
-let _windows = [];
+var _lastFocusedWindow = null;
+var _windows = [];
// Exported symbol
@@ -64,7 +64,7 @@ function _handleEvent(aEvent) {
// Methods that impact a browser. Put into single object for organization.
-let BrowserHelper = {
+var BrowserHelper = {
onOpen: function NP_BH_onOpen(aBrowser) {
// If the tab is in the focused window, leave priority as it is
if (aBrowser.ownerDocument.defaultView != _lastFocusedWindow)
@@ -91,7 +91,7 @@ let BrowserHelper = {
// Methods that impact a window. Put into single object for organization.
-let WindowHelper = {
+var WindowHelper = {
addWindow: function NP_WH_addWindow(aWindow) {
// Build internal data object
_windows.push({ window: aWindow, lastSelectedBrowser: null });
diff --git a/application/palemoon/modules/PopupNotifications.jsm b/application/palemoon/modules/PopupNotifications.jsm
index 9b2e8e5d1..d2faf52c3 100644
--- a/application/palemoon/modules/PopupNotifications.jsm
+++ b/application/palemoon/modules/PopupNotifications.jsm
@@ -18,8 +18,8 @@ const ICON_ATTRIBUTE_SHOWING = "showing";
const PREF_SECURITY_DELAY = "security.notification_enable_delay";
-let popupNotificationsMap = new WeakMap();
-let gNotificationParents = new WeakMap;
+var popupNotificationsMap = new WeakMap();
+var gNotificationParents = new WeakMap;
function getAnchorFromBrowser(aBrowser) {
let anchor = aBrowser.getAttribute("popupnotificationanchor") ||
diff --git a/application/palemoon/modules/SharedFrame.jsm b/application/palemoon/modules/SharedFrame.jsm
index 4d248ae5b..b9d59bfa9 100644
--- a/application/palemoon/modules/SharedFrame.jsm
+++ b/application/palemoon/modules/SharedFrame.jsm
@@ -18,7 +18,7 @@ const Cu = Components.utils;
* when another one of the placeholder is meant to be displayed.
* */
-let Frames = new Map();
+var Frames = new Map();
/**
* The Frames map is the main data structure that holds information
diff --git a/application/palemoon/modules/openLocationLastURL.jsm b/application/palemoon/modules/openLocationLastURL.jsm
index 0d653df28..3f58db8ce 100644
--- a/application/palemoon/modules/openLocationLastURL.jsm
+++ b/application/palemoon/modules/openLocationLastURL.jsm
@@ -10,11 +10,11 @@ Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
this.EXPORTED_SYMBOLS = [ "OpenLocationLastURL" ];
-let prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
+var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
-let gOpenLocationLastURLData = "";
+var gOpenLocationLastURLData = "";
-let observer = {
+var observer = {
QueryInterface: function (aIID) {
if (aIID.equals(Components.interfaces.nsIObserver) ||
aIID.equals(Components.interfaces.nsISupports) ||
@@ -35,7 +35,7 @@ let observer = {
}
};
-let os = Components.classes["@mozilla.org/observer-service;1"]
+var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
os.addObserver(observer, "last-pb-context-exited", true);
os.addObserver(observer, "browser:purge-session-history", true);
diff --git a/application/palemoon/modules/promise.js b/application/palemoon/modules/promise.js
index 7c96f02cf..74065c8db 100644
--- a/application/palemoon/modules/promise.js
+++ b/application/palemoon/modules/promise.js
@@ -24,7 +24,7 @@ module.metadata = {
'stability': 'unstable'
};
-let promised = (function() {
+var promised = (function() {
// Note: Define shortcuts and utility functions here in order to avoid
// slower property accesses and unnecessary closure creations on each
// call of this popular function.
diff --git a/application/palemoon/themes/linux/browser.css b/application/palemoon/themes/linux/browser.css
index a396ea5fd..334062613 100644
--- a/application/palemoon/themes/linux/browser.css
+++ b/application/palemoon/themes/linux/browser.css
@@ -1559,29 +1559,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
min-width: 27em;
}
-.panel-promo-box {
- margin: 8px -10px -10px -10px;
- padding: 8px 10px;
- border-top: 1px solid ThreeDShadow;
- background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
-}
-
-.panel-promo-icon {
- list-style-image: url("chrome://browser/skin/sync-notification-24.png");
- -moz-margin-end: 10px;
- vertical-align: middle;
-}
-
-.panel-promo-closebutton {
- margin-top: 0;
- margin-bottom: 0;
-}
-
-.panel-promo-closebutton > .toolbarbutton-text {
- padding: 0;
- margin: 0;
-}
-
/* Content area */
#sidebar {
background-color: Window;
diff --git a/application/palemoon/themes/linux/jar.mn b/application/palemoon/themes/linux/jar.mn
index 44d837778..f1339b803 100644
--- a/application/palemoon/themes/linux/jar.mn
+++ b/application/palemoon/themes/linux/jar.mn
@@ -130,7 +130,6 @@ browser.jar:
skin/classic/browser/sync-128.png
skin/classic/browser/sync-desktopIcon.png
skin/classic/browser/sync-mobileIcon.png
- skin/classic/browser/sync-notification-24.png
skin/classic/browser/syncSetup.css
skin/classic/browser/syncCommon.css
skin/classic/browser/syncQuota.css
diff --git a/application/palemoon/themes/linux/sync-notification-24.png b/application/palemoon/themes/linux/sync-notification-24.png
deleted file mode 100644
index d67eb47ac..000000000
--- a/application/palemoon/themes/linux/sync-notification-24.png
+++ /dev/null
Binary files differ
diff --git a/application/palemoon/themes/osx/browser.css b/application/palemoon/themes/osx/browser.css
index aa5918bab..a66ddacfc 100644
--- a/application/palemoon/themes/osx/browser.css
+++ b/application/palemoon/themes/osx/browser.css
@@ -1338,41 +1338,6 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
min-width: 27em;
}
-.panel-promo-box {
- margin: 10px -10px -10px;
- padding: 8px 10px;
- border-top: 1px solid ThreeDShadow;
- background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
- border-bottom-left-radius: 3px;
- border-bottom-right-radius: 3px;
-}
-
-@media (-moz-mac-lion-theme) {
- .panel-promo-box {
- border-top-style: none;
- background: #f1f5fb;
- color: GrayText;
- box-shadow: 0px 1px 2px rgb(204,214,234) inset;
- }
-}
-
-.panel-promo-icon {
- list-style-image: url("chrome://browser/skin/sync-notification-24.png");
- -moz-margin-end: 10px;
- vertical-align: middle;
-}
-
-.panel-promo-closebutton {
- border: none;
- -moz-margin-end: -14px;
- margin-top: -8px;
-}
-
-.panel-promo-closebutton > .toolbarbutton-text {
- padding: 0;
- margin: 0;
-}
-
/* ::::: content area ::::: */
#sidebar {
diff --git a/application/palemoon/themes/osx/jar.mn b/application/palemoon/themes/osx/jar.mn
index 8742f1b87..904da3788 100644
--- a/application/palemoon/themes/osx/jar.mn
+++ b/application/palemoon/themes/osx/jar.mn
@@ -163,7 +163,6 @@ browser.jar:
skin/classic/browser/sync-bg.png
skin/classic/browser/sync-desktopIcon.png
skin/classic/browser/sync-mobileIcon.png
- skin/classic/browser/sync-notification-24.png
skin/classic/browser/syncSetup.css
skin/classic/browser/syncCommon.css
skin/classic/browser/syncQuota.css
diff --git a/application/palemoon/themes/osx/sync-notification-24.png b/application/palemoon/themes/osx/sync-notification-24.png
deleted file mode 100644
index fc9a4e63d..000000000
--- a/application/palemoon/themes/osx/sync-notification-24.png
+++ /dev/null
Binary files differ
diff --git a/application/palemoon/themes/windows/browser.css b/application/palemoon/themes/windows/browser.css
index 3e8c63af4..a64955f0f 100644
--- a/application/palemoon/themes/windows/browser.css
+++ b/application/palemoon/themes/windows/browser.css
@@ -1778,42 +1778,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
min-width: 27em;
}
-.panel-promo-box {
- margin: 10px -10px -10px;
- padding: 8px 10px;
- border-top: 1px solid ThreeDShadow;
- background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
- border-bottom-left-radius: 3px;
- border-bottom-right-radius: 3px;
-}
-
-@media (-moz-windows-default-theme) {
- .panel-promo-box {
- border-top-style: none;
- background: #f1f5fb;
- color: GrayText;
- box-shadow: 0px 1px 2px rgb(204,214,234) inset;
- }
-}
-
-.panel-promo-icon {
- list-style-image: url("chrome://browser/skin/sync-notification-24.png");
- -moz-margin-end: 10px;
- vertical-align: middle;
-}
-
-.panel-promo-closebutton {
- -moz-appearance: none;
- border: none;
- -moz-margin-end: -10px;
- margin-top: -5px;
-}
-
-.panel-promo-closebutton > .toolbarbutton-text {
- padding: 0;
- margin: 0;
-}
-
/* ::::: content area ::::: */
#sidebar {
diff --git a/application/palemoon/themes/windows/jar.mn b/application/palemoon/themes/windows/jar.mn
index 994e87be3..30643570b 100644
--- a/application/palemoon/themes/windows/jar.mn
+++ b/application/palemoon/themes/windows/jar.mn
@@ -159,7 +159,6 @@ browser.jar:
skin/classic/browser/sync-bg.png
skin/classic/browser/sync-desktopIcon.png
skin/classic/browser/sync-mobileIcon.png
- skin/classic/browser/sync-notification-24.png
skin/classic/browser/syncSetup.css
skin/classic/browser/syncCommon.css
skin/classic/browser/syncQuota.css
diff --git a/application/palemoon/themes/windows/sync-notification-24.png b/application/palemoon/themes/windows/sync-notification-24.png
deleted file mode 100644
index fc9a4e63d..000000000
--- a/application/palemoon/themes/windows/sync-notification-24.png
+++ /dev/null
Binary files differ
diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
index a05b031b2..f03f21c3f 100644
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -557,7 +557,7 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks {
transition: none;
}
-#DateTimePickerPanel {
+#DateTimePickerPanel[active="true"] {
-moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
}
@@ -815,7 +815,6 @@ html|*#fullscreen-exit-button {
.popup-anchor {
/* should occupy space but not be visible */
opacity: 0;
- visibility: hidden;
pointer-events: none;
-moz-stack-sizing: ignore;
}
diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul
index ae531e167..5879f2a29 100644
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -155,13 +155,17 @@
level="parent"
overflowpadding="30" />
+ <!-- for date/time picker. consumeoutsideclicks is set to never, so that
+ clicks on the anchored input box are never consumed. -->
<panel id="DateTimePickerPanel"
type="arrow"
hidden="true"
orient="vertical"
noautofocus="true"
- consumeoutsideclicks="false"
- level="parent">
+ norolluponanchor="true"
+ consumeoutsideclicks="never"
+ level="parent"
+ tabspecific="true">
<iframe id="dateTimePopupFrame"/>
</panel>
diff --git a/docshell/test/unit/test_nsDefaultURIFixup_info.js b/docshell/test/unit/test_nsDefaultURIFixup_info.js
index 9e33ea484..c606ac32e 100644
--- a/docshell/test/unit/test_nsDefaultURIFixup_info.js
+++ b/docshell/test/unit/test_nsDefaultURIFixup_info.js
@@ -426,8 +426,6 @@ var testcases = [ {
protocolChange: true,
}, {
input: "?'.com",
- fixedURI: "http:///?%27.com",
- alternateURI: "http://www..com/?%27.com",
keywordLookup: true,
protocolChange: true,
}, {
@@ -436,14 +434,10 @@ var testcases = [ {
protocolChange: true
}, {
input: "?mozilla",
- fixedURI: "http:///?mozilla",
- alternateURI: "http://www..com/?mozilla",
keywordLookup: true,
protocolChange: true,
}, {
input: "??mozilla",
- fixedURI: "http:///??mozilla",
- alternateURI: "http://www..com/??mozilla",
keywordLookup: true,
protocolChange: true,
}, {
diff --git a/docshell/test/unit/test_nsDefaultURIFixup_search.js b/docshell/test/unit/test_nsDefaultURIFixup_search.js
index c00b6a85f..c84452b5d 100644
--- a/docshell/test/unit/test_nsDefaultURIFixup_search.js
+++ b/docshell/test/unit/test_nsDefaultURIFixup_search.js
@@ -74,7 +74,7 @@ var data = [
},
{
wrong: 'user:@example.com:8080/this/is/a/test.html',
- fixed: 'http://user:@example.com:8080/this/is/a/test.html',
+ fixed: 'http://user@example.com:8080/this/is/a/test.html',
},
{
wrong: '//user:pass@example.com:8080/this/is/a/test.html',
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index 15df2b337..a38c38293 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -105,6 +105,7 @@
#include "mozilla/dom/CanvasPath.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/SVGImageElement.h"
#include "mozilla/dom/SVGMatrix.h"
#include "mozilla/dom/TextMetrics.h"
#include "mozilla/dom/SVGMatrix.h"
@@ -2477,10 +2478,10 @@ CanvasRenderingContext2D::CreatePattern(const CanvasImageSource& aSource,
return nullptr;
}
- Element* htmlElement;
+ Element* element;
if (aSource.IsHTMLCanvasElement()) {
HTMLCanvasElement* canvas = &aSource.GetAsHTMLCanvasElement();
- htmlElement = canvas;
+ element = canvas;
nsIntSize size = canvas->GetSize();
if (size.width == 0 || size.height == 0) {
@@ -2505,7 +2506,7 @@ CanvasRenderingContext2D::CreatePattern(const CanvasImageSource& aSource,
}
RefPtr<CanvasPattern> pat =
- new CanvasPattern(this, srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false);
+ new CanvasPattern(this, srcSurf, repeatMode, element->NodePrincipal(), canvas->IsWriteOnly(), false);
return pat.forget();
}
@@ -2516,11 +2517,19 @@ CanvasRenderingContext2D::CreatePattern(const CanvasImageSource& aSource,
return nullptr;
}
- htmlElement = img;
+ element = img;
+ } else if (aSource.IsSVGImageElement()) {
+ SVGImageElement* img = &aSource.GetAsSVGImageElement();
+ if (img->IntrinsicState().HasState(NS_EVENT_STATE_BROKEN)) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ element = img;
} else if (aSource.IsHTMLVideoElement()) {
auto& video = aSource.GetAsHTMLVideoElement();
video.MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN);
- htmlElement = &video;
+ element = &video;
} else {
// Special case for ImageBitmap
ImageBitmap& imgBitmap = aSource.GetAsImageBitmap();
@@ -2559,7 +2568,7 @@ CanvasRenderingContext2D::CreatePattern(const CanvasImageSource& aSource,
// The canvas spec says that createPattern should use the first frame
// of animated images
nsLayoutUtils::SurfaceFromElementResult res =
- nsLayoutUtils::SurfaceFromElement(htmlElement,
+ nsLayoutUtils::SurfaceFromElement(element,
nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget);
if (!res.GetSourceSurface()) {
@@ -4949,6 +4958,9 @@ CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
if (aImage.IsHTMLImageElement()) {
HTMLImageElement* img = &aImage.GetAsHTMLImageElement();
element = img;
+ } else if (aImage.IsSVGImageElement()) {
+ SVGImageElement* img = &aImage.GetAsSVGImageElement();
+ element = img;
} else {
HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement();
video->MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE);
diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h
index c3ee3bdcb..d5dff8f3b 100644
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -38,8 +38,8 @@ class SourceSurface;
} // namespace gl
namespace dom {
-class HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap;
-typedef HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap CanvasImageSource;
+class HTMLImageElementOrSVGImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap;
+typedef HTMLImageElementOrSVGImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap CanvasImageSource;
class ImageData;
class StringOrCanvasGradientOrCanvasPattern;
class OwningStringOrCanvasGradientOrCanvasPattern;
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
index d46eccdbc..52062c4b9 100644
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -114,15 +114,12 @@
#include <limits>
#include "nsIColorPicker.h"
-#include "nsIDatePicker.h"
#include "nsIStringEnumerator.h"
#include "HTMLSplitOnSpacesTokenizer.h"
#include "nsIController.h"
#include "nsIMIMEInfo.h"
#include "nsFrameSelection.h"
-#include "nsIConsoleService.h"
-
// input type=date
#include "js/Date.h"
@@ -543,9 +540,8 @@ GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
bool
HTMLInputElement::ValueAsDateEnabled(JSContext* cx, JSObject* obj)
{
- return Preferences::GetBool("dom.experimental_forms", false) ||
- Preferences::GetBool("dom.forms.datepicker", false) ||
- Preferences::GetBool("dom.forms.datetime", false);
+ return IsExperimentalFormsEnabled() || IsInputDateTimeEnabled() ||
+ IsInputDateTimeOthersEnabled();
}
NS_IMETHODIMP
@@ -628,7 +624,7 @@ HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
new DispatchChangeEventCallback(mInput);
- if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ if (IsWebkitDirPickerEnabled() &&
mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
ErrorResult error;
GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
@@ -747,59 +743,6 @@ nsColorPickerShownCallback::Done(const nsAString& aColor)
NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
-class DatePickerShownCallback final : public nsIDatePickerShownCallback
-{
- ~DatePickerShownCallback() {}
-public:
- DatePickerShownCallback(HTMLInputElement* aInput,
- nsIDatePicker* aDatePicker)
- : mInput(aInput)
- , mDatePicker(aDatePicker)
- {}
-
- NS_DECL_ISUPPORTS
-
- NS_IMETHOD Done(const nsAString& aDate) override;
- NS_IMETHOD Cancel() override;
-
-private:
- RefPtr<HTMLInputElement> mInput;
- nsCOMPtr<nsIDatePicker> mDatePicker;
-};
-
-NS_IMETHODIMP
-DatePickerShownCallback::Cancel()
-{
- mInput->PickerClosed();
- return NS_OK;
-}
-
-NS_IMETHODIMP
-DatePickerShownCallback::Done(const nsAString& aDate)
-{
- nsAutoString oldValue;
-
- mInput->PickerClosed();
- mInput->GetValue(oldValue);
-
- if(!oldValue.Equals(aDate)){
- mInput->SetValue(aDate);
- nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
- static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
- NS_LITERAL_STRING("input"), true,
- false);
- return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
- static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
- NS_LITERAL_STRING("change"), true,
- false);
- }
-
- return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(DatePickerShownCallback, nsIDatePickerShownCallback)
-
-
bool
HTMLInputElement::IsPopupBlocked() const
{
@@ -825,56 +768,6 @@ HTMLInputElement::IsPopupBlocked() const
}
nsresult
-HTMLInputElement::InitDatePicker()
-{
- if (!Preferences::GetBool("dom.forms.datepicker", false)) {
- return NS_OK;
- }
-
- if (mPickerRunning) {
- NS_WARNING("Just one nsIDatePicker is allowed");
- return NS_ERROR_FAILURE;
- }
-
- nsCOMPtr<nsIDocument> doc = OwnerDoc();
-
- nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
- if (!win) {
- return NS_ERROR_FAILURE;
- }
-
- if (IsPopupBlocked()) {
- win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
- return NS_OK;
- }
-
- // Get Loc title
- nsXPIDLString title;
- nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
- "DatePicker", title);
-
- nsresult rv;
- nsCOMPtr<nsIDatePicker> datePicker = do_CreateInstance("@mozilla.org/datepicker;1", &rv);
- if (!datePicker) {
- return rv;
- }
-
- nsAutoString initialValue;
- GetValueInternal(initialValue);
- rv = datePicker->Init(win, title, initialValue);
-
- nsCOMPtr<nsIDatePickerShownCallback> callback =
- new DatePickerShownCallback(this, datePicker);
-
- rv = datePicker->Open(callback);
- if (NS_SUCCEEDED(rv)) {
- mPickerRunning = true;
- }
-
- return rv;
-}
-
-nsresult
HTMLInputElement::InitColorPicker()
{
if (mPickerRunning) {
@@ -1919,6 +1812,22 @@ HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
aResultValue = Decimal::fromDouble(days * kMsPerDay);
return true;
}
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ {
+ uint32_t year, month, day, timeInMs;
+ if (!ParseDateTimeLocal(aValue, &year, &month, &day, &timeInMs)) {
+ return false;
+ }
+
+ JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day,
+ timeInMs));
+ if (!time.isValid()) {
+ return false;
+ }
+
+ aResultValue = Decimal::fromDouble(time.toDouble());
+ return true;
+ }
default:
MOZ_ASSERT(false, "Unrecognized input type");
return false;
@@ -2108,21 +2017,17 @@ HTMLInputElement::ConvertNumberToString(Decimal aValue,
}
case NS_FORM_INPUT_TIME:
{
+ aValue = aValue.floor();
// Per spec, we need to truncate |aValue| and we should only represent
// times inside a day [00:00, 24:00[, which means that we should do a
// modulo on |aValue| using the number of milliseconds in a day (86400000).
- uint32_t value = NS_floorModulo(aValue.floor(), Decimal(86400000)).toDouble();
-
- uint16_t milliseconds = value % 1000;
- value /= 1000;
+ uint32_t value =
+ NS_floorModulo(aValue, Decimal::fromDouble(kMsPerDay)).toDouble();
- uint8_t seconds = value % 60;
- value /= 60;
-
- uint8_t minutes = value % 60;
- value /= 60;
-
- uint8_t hours = value;
+ uint16_t milliseconds, seconds, minutes, hours;
+ if (!GetTimeFromMs(value, &hours, &minutes, &seconds, &milliseconds)) {
+ return false;
+ }
if (milliseconds != 0) {
aResultString.AppendPrintf("%02d:%02d:%02d.%03d",
@@ -2192,6 +2097,42 @@ HTMLInputElement::ConvertNumberToString(Decimal aValue,
aResultString.AppendPrintf("%04.0f-W%02d", year, week);
return true;
}
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ {
+ aValue = aValue.floor();
+
+ uint32_t timeValue =
+ NS_floorModulo(aValue, Decimal::fromDouble(kMsPerDay)).toDouble();
+
+ uint16_t milliseconds, seconds, minutes, hours;
+ if (!GetTimeFromMs(timeValue,
+ &hours, &minutes, &seconds, &milliseconds)) {
+ return false;
+ }
+
+ double year = JS::YearFromTime(aValue.toDouble());
+ double month = JS::MonthFromTime(aValue.toDouble());
+ double day = JS::DayFromTime(aValue.toDouble());
+
+ if (IsNaN(year) || IsNaN(month) || IsNaN(day)) {
+ return false;
+ }
+
+ if (milliseconds != 0) {
+ aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d.%03d",
+ year, month + 1, day, hours, minutes,
+ seconds, milliseconds);
+ } else if (seconds != 0) {
+ aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d:%02d",
+ year, month + 1, day, hours, minutes,
+ seconds);
+ } else {
+ aResultString.AppendPrintf("%04.0f-%02.0f-%02.0fT%02d:%02d",
+ year, month + 1, day, hours, minutes);
+ }
+
+ return true;
+ }
default:
MOZ_ASSERT(false, "Unrecognized input type");
return false;
@@ -2202,8 +2143,7 @@ HTMLInputElement::ConvertNumberToString(Decimal aValue,
Nullable<Date>
HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
{
- // TODO: this is temporary until bug 888331 is fixed.
- if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ if (!IsDateTimeInputType(mType)) {
return Nullable<Date>();
}
@@ -2261,6 +2201,19 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
return Nullable<Date>(Date(time));
}
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ {
+ uint32_t year, month, day, timeInMs;
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!ParseDateTimeLocal(value, &year, &month, &day, &timeInMs)) {
+ return Nullable<Date>();
+ }
+
+ JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day,
+ timeInMs));
+ return Nullable<Date>(Date(time));
+ }
}
MOZ_ASSERT(false, "Unrecognized input type");
@@ -2271,8 +2224,7 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
void
HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
{
- // TODO: this is temporary until bug 888331 is fixed.
- if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ if (!IsDateTimeInputType(mType)) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
@@ -2380,11 +2332,8 @@ HTMLInputElement::GetMaximum() const
Decimal
HTMLInputElement::GetStepBase() const
{
- MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER ||
- mType == NS_FORM_INPUT_DATE ||
- mType == NS_FORM_INPUT_TIME ||
- mType == NS_FORM_INPUT_MONTH ||
- mType == NS_FORM_INPUT_WEEK ||
+ MOZ_ASSERT(IsDateTimeInputType(mType) ||
+ mType == NS_FORM_INPUT_NUMBER ||
mType == NS_FORM_INPUT_RANGE,
"Check that kDefaultStepBase is correct for this new type");
@@ -2515,11 +2464,8 @@ HTMLInputElement::ApplyStep(int32_t aStep)
bool
HTMLInputElement::IsExperimentalMobileType(uint8_t aType)
{
- return (aType == NS_FORM_INPUT_DATE &&
- !Preferences::GetBool("dom.forms.datetime", false) &&
- !Preferences::GetBool("dom.forms.datepicker", false)) ||
- (aType == NS_FORM_INPUT_TIME &&
- !Preferences::GetBool("dom.forms.datetime", false));
+ return (aType == NS_FORM_INPUT_DATE || aType == NS_FORM_INPUT_TIME) &&
+ !IsInputDateTimeEnabled();
}
bool
@@ -2832,7 +2778,8 @@ HTMLInputElement::GetOwnerDateTimeControl()
HTMLInputElement::FromContentOrNull(
GetParent()->GetParent()->GetParent()->GetParent());
if (ownerDateTimeControl &&
- ownerDateTimeControl->mType == NS_FORM_INPUT_TIME) {
+ (ownerDateTimeControl->mType == NS_FORM_INPUT_TIME ||
+ ownerDateTimeControl->mType == NS_FORM_INPUT_DATE)) {
return ownerDateTimeControl;
}
}
@@ -3024,8 +2971,8 @@ HTMLInputElement::GetDisplayFileName(nsAString& aValue) const
nsXPIDLString value;
if (mFilesOrDirectories.IsEmpty()) {
- if ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
- (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ if ((IsDirPickerEnabled() && Allowdirs()) ||
+ (IsWebkitDirPickerEnabled() &&
HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
"NoDirSelected", value);
@@ -3054,7 +3001,7 @@ HTMLInputElement::SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& a
{
ClearGetFilesHelpers();
- if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+ if (IsWebkitFileSystemEnabled()) {
HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
mEntries.Clear();
}
@@ -3073,7 +3020,7 @@ HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
mFilesOrDirectories.Clear();
ClearGetFilesHelpers();
- if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+ if (IsWebkitFileSystemEnabled()) {
HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
mEntries.Clear();
}
@@ -3096,14 +3043,14 @@ HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirect
{
SetFilesOrDirectories(aFilesOrDirectories, true);
- if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+ if (IsWebkitFileSystemEnabled()) {
UpdateEntries(aFilesOrDirectories);
}
RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
new DispatchChangeEventCallback(this);
- if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ if (IsWebkitDirPickerEnabled() &&
HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
ErrorResult rv;
GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
@@ -3181,8 +3128,8 @@ HTMLInputElement::GetFiles()
return nullptr;
}
- if (Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs() &&
- (!Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) ||
+ if (IsDirPickerEnabled() && Allowdirs() &&
+ (!IsWebkitDirPickerEnabled() ||
!HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
return nullptr;
}
@@ -3282,7 +3229,8 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
if (frame) {
frame->UpdateForValueChange();
}
- } else if (mType == NS_FORM_INPUT_TIME &&
+ } else if ((mType == NS_FORM_INPUT_TIME ||
+ mType == NS_FORM_INPUT_DATE) &&
!IsExperimentalMobileType(mType)) {
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
if (frame) {
@@ -3591,7 +3539,8 @@ HTMLInputElement::Blur(ErrorResult& aError)
}
}
- if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
+ if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
+ !IsExperimentalMobileType(mType)) {
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
if (frame) {
frame->HandleBlurEvent();
@@ -3618,7 +3567,8 @@ HTMLInputElement::Focus(ErrorResult& aError)
}
}
- if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
+ if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
+ !IsExperimentalMobileType(mType)) {
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
if (frame) {
frame->HandleFocusEvent();
@@ -3956,7 +3906,7 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
}
}
- if (mType == NS_FORM_INPUT_TIME &&
+ if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
!IsExperimentalMobileType(mType) &&
aVisitor.mEvent->mMessage == eFocus &&
aVisitor.mEvent->mOriginalTarget == this) {
@@ -4083,7 +4033,8 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
// Stop the event if the related target's first non-native ancestor is the
// same as the original target's first non-native ancestor (we are moving
// inside of the same element).
- if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType) &&
+ if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
+ !IsExperimentalMobileType(mType) &&
(aVisitor.mEvent->mMessage == eFocus ||
aVisitor.mEvent->mMessage == eFocusIn ||
aVisitor.mEvent->mMessage == eFocusOut ||
@@ -4361,8 +4312,8 @@ HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor)
do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
if (target &&
target->FindFirstNonChromeOnlyAccessContent() == this &&
- ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
- (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ ((IsDirPickerEnabled() && Allowdirs()) ||
+ (IsWebkitDirPickerEnabled() &&
HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
type = FILE_PICKER_DIRECTORY;
}
@@ -4371,9 +4322,6 @@ HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor)
if (mType == NS_FORM_INPUT_COLOR) {
return InitColorPicker();
}
- if (mType == NS_FORM_INPUT_DATE) {
- return InitDatePicker();
- }
return NS_OK;
}
@@ -5383,6 +5331,29 @@ HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const
}
bool
+HTMLInputElement::GetTimeFromMs(double aValue, uint16_t* aHours,
+ uint16_t* aMinutes, uint16_t* aSeconds,
+ uint16_t* aMilliseconds) const {
+ MOZ_ASSERT(aValue >= 0 && aValue < kMsPerDay,
+ "aValue must be milliseconds within a day!");
+
+ uint32_t value = floor(aValue);
+
+ *aMilliseconds = value % 1000;
+ value /= 1000;
+
+ *aSeconds = value % 60;
+ value /= 60;
+
+ *aMinutes = value % 60;
+ value /= 60;
+
+ *aHours = value;
+
+ return true;
+}
+
+bool
HTMLInputElement::IsValidWeek(const nsAString& aValue) const
{
uint32_t year, week;
@@ -5730,20 +5701,131 @@ HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult)
return true;
}
-static bool
-IsDateTimeEnabled(int32_t aNewType)
+/* static */ bool
+HTMLInputElement::IsDateTimeTypeSupported(uint8_t aDateTimeInputType)
+{
+ return ((aDateTimeInputType == NS_FORM_INPUT_DATE ||
+ aDateTimeInputType == NS_FORM_INPUT_TIME) &&
+ (IsInputDateTimeEnabled() || IsExperimentalFormsEnabled())) ||
+ ((aDateTimeInputType == NS_FORM_INPUT_MONTH ||
+ aDateTimeInputType == NS_FORM_INPUT_WEEK ||
+ aDateTimeInputType == NS_FORM_INPUT_DATETIME_LOCAL) &&
+ IsInputDateTimeOthersEnabled());
+}
+
+/* static */ bool
+HTMLInputElement::IsWebkitDirPickerEnabled()
{
- return (aNewType == NS_FORM_INPUT_DATE &&
- (Preferences::GetBool("dom.forms.datetime", false) ||
- Preferences::GetBool("dom.experimental_forms", false) ||
- Preferences::GetBool("dom.forms.datepicker", false))) ||
- (aNewType == NS_FORM_INPUT_TIME &&
- (Preferences::GetBool("dom.forms.datetime", false) ||
- Preferences::GetBool("dom.experimental_forms", false))) ||
- ((aNewType == NS_FORM_INPUT_MONTH ||
- aNewType == NS_FORM_INPUT_WEEK ||
- aNewType == NS_FORM_INPUT_DATETIME_LOCAL) &&
- Preferences::GetBool("dom.forms.datetime", false));
+ static bool sWebkitDirPickerEnabled = false;
+ static bool sWebkitDirPickerPrefCached = false;
+ if (!sWebkitDirPickerPrefCached) {
+ sWebkitDirPickerPrefCached = true;
+ Preferences::AddBoolVarCache(&sWebkitDirPickerEnabled,
+ "dom.webkitBlink.dirPicker.enabled",
+ false);
+ }
+
+ return sWebkitDirPickerEnabled;
+}
+
+/* static */ bool
+HTMLInputElement::IsWebkitFileSystemEnabled()
+{
+ static bool sWebkitFileSystemEnabled = false;
+ static bool sWebkitFileSystemPrefCached = false;
+ if (!sWebkitFileSystemPrefCached) {
+ sWebkitFileSystemPrefCached = true;
+ Preferences::AddBoolVarCache(&sWebkitFileSystemEnabled,
+ "dom.webkitBlink.filesystem.enabled",
+ false);
+ }
+
+ return sWebkitFileSystemEnabled;
+}
+
+/* static */ bool
+HTMLInputElement::IsDirPickerEnabled()
+{
+ static bool sDirPickerEnabled = false;
+ static bool sDirPickerPrefCached = false;
+ if (!sDirPickerPrefCached) {
+ sDirPickerPrefCached = true;
+ Preferences::AddBoolVarCache(&sDirPickerEnabled, "dom.input.dirpicker",
+ false);
+ }
+
+ return sDirPickerEnabled;
+}
+
+/* static */ bool
+HTMLInputElement::IsExperimentalFormsEnabled()
+{
+ static bool sExperimentalFormsEnabled = false;
+ static bool sExperimentalFormsPrefCached = false;
+ if (!sExperimentalFormsPrefCached) {
+ sExperimentalFormsPrefCached = true;
+ Preferences::AddBoolVarCache(&sExperimentalFormsEnabled,
+ "dom.experimental_forms",
+ false);
+ }
+
+ return sExperimentalFormsEnabled;
+}
+
+/* static */ bool
+HTMLInputElement::IsInputDateTimeEnabled()
+{
+ static bool sDateTimeEnabled = false;
+ static bool sDateTimePrefCached = false;
+ if (!sDateTimePrefCached) {
+ sDateTimePrefCached = true;
+ Preferences::AddBoolVarCache(&sDateTimeEnabled, "dom.forms.datetime",
+ false);
+ }
+
+ return sDateTimeEnabled;
+}
+
+/* static */ bool
+HTMLInputElement::IsInputDateTimeOthersEnabled()
+{
+ static bool sDateTimeOthersEnabled = false;
+ static bool sDateTimeOthersPrefCached = false;
+ if (!sDateTimeOthersPrefCached) {
+ sDateTimeOthersPrefCached = true;
+ Preferences::AddBoolVarCache(&sDateTimeOthersEnabled,
+ "dom.forms.datetime.others", false);
+ }
+
+ return sDateTimeOthersEnabled;
+}
+
+/* static */ bool
+HTMLInputElement::IsInputNumberEnabled()
+{
+ static bool sInputNumberEnabled = false;
+ static bool sInputNumberPrefCached = false;
+ if (!sInputNumberPrefCached) {
+ sInputNumberPrefCached = true;
+ Preferences::AddBoolVarCache(&sInputNumberEnabled, "dom.forms.number",
+ false);
+ }
+
+ return sInputNumberEnabled;
+}
+
+/* static */ bool
+HTMLInputElement::IsInputColorEnabled()
+{
+ static bool sInputColorEnabled = false;
+ static bool sInputColorPrefCached = false;
+ if (!sInputColorPrefCached) {
+ sInputColorPrefCached = true;
+ Preferences::AddBoolVarCache(&sInputColorEnabled, "dom.forms.color",
+ false);
+ }
+
+ return sInputColorEnabled;
}
bool
@@ -5760,13 +5842,9 @@ HTMLInputElement::ParseAttribute(int32_t aNamespaceID,
bool success = aResult.ParseEnumValue(aValue, kInputTypeTable, false);
if (success) {
newType = aResult.GetEnumValue();
- if ((IsExperimentalMobileType(newType) &&
- !Preferences::GetBool("dom.experimental_forms", false)) ||
- (newType == NS_FORM_INPUT_NUMBER &&
- !Preferences::GetBool("dom.forms.number", false)) ||
- (newType == NS_FORM_INPUT_COLOR &&
- !Preferences::GetBool("dom.forms.color", false)) ||
- (IsDateTimeInputType(newType) && !IsDateTimeEnabled(newType))) {
+ if ((newType == NS_FORM_INPUT_NUMBER && !IsInputNumberEnabled()) ||
+ (newType == NS_FORM_INPUT_COLOR && !IsInputColorEnabled()) ||
+ (IsDateTimeInputType(newType) && !IsDateTimeTypeSupported(newType))) {
newType = kInputDefaultType->value;
aResult.SetTo(newType, &aValue);
}
@@ -7161,13 +7239,15 @@ HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t*
if (mType == NS_FORM_INPUT_FILE ||
mType == NS_FORM_INPUT_NUMBER ||
- mType == NS_FORM_INPUT_TIME) {
+ mType == NS_FORM_INPUT_TIME ||
+ mType == NS_FORM_INPUT_DATE) {
if (aTabIndex) {
// We only want our native anonymous child to be tabable to, not ourself.
*aTabIndex = -1;
}
if (mType == NS_FORM_INPUT_NUMBER ||
- mType == NS_FORM_INPUT_TIME) {
+ mType == NS_FORM_INPUT_TIME ||
+ mType == NS_FORM_INPUT_DATE) {
*aIsFocusable = true;
} else {
*aIsFocusable = defaultFocusable;
@@ -7650,8 +7730,7 @@ HTMLInputElement::HasPatternMismatch() const
bool
HTMLInputElement::IsRangeOverflow() const
{
- // TODO: this is temporary until bug 888331 is fixed.
- if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ if (!DoesMinMaxApply()) {
return false;
}
@@ -7671,8 +7750,7 @@ HTMLInputElement::IsRangeOverflow() const
bool
HTMLInputElement::IsRangeUnderflow() const
{
- // TODO: this is temporary until bug 888331 is fixed.
- if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ if (!DoesMinMaxApply()) {
return false;
}
@@ -8622,6 +8700,7 @@ HTMLInputElement::GetStepScaleFactor() const
case NS_FORM_INPUT_RANGE:
return kStepScaleFactorNumberRange;
case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
return kStepScaleFactorTime;
case NS_FORM_INPUT_MONTH:
return kStepScaleFactorMonth;
@@ -8646,6 +8725,7 @@ HTMLInputElement::GetDefaultStep() const
case NS_FORM_INPUT_RANGE:
return kDefaultStep;
case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
return kDefaultStepTime;
default:
MOZ_ASSERT(false, "Unrecognized input type");
@@ -8680,8 +8760,7 @@ HTMLInputElement::UpdateHasRange()
mHasRange = false;
- // TODO: this is temporary until bug 888331 is fixed.
- if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ if (!DoesMinMaxApply()) {
return;
}
diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h
index 9ca876aee..98a590443 100644
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -796,6 +796,15 @@ public:
void UpdateDateTimePicker(const DateTimeValue& aValue);
void CloseDateTimePicker();
+ /*
+ * The following are called from datetime input box binding to get the
+ * corresponding computed values.
+ */
+ double GetStepAsDouble() { return GetStep().toDouble(); }
+ double GetStepBaseAsDouble() { return GetStepBase().toDouble(); }
+ double GetMinimumAsDouble() { return GetMinimum().toDouble(); }
+ double GetMaximumAsDouble() { return GetMaximum().toDouble(); }
+
HTMLInputElement* GetOwnerNumberControl();
HTMLInputElement* GetOwnerDateTimeControl();
@@ -1061,11 +1070,7 @@ protected:
/**
* Returns if the step attribute apply for the current type.
*/
- bool DoesStepApply() const
- {
- // TODO: this is temporary until bug 888331 is fixed.
- return DoesMinMaxApply() && mType != NS_FORM_INPUT_DATETIME_LOCAL;
- }
+ bool DoesStepApply() const { return DoesMinMaxApply(); }
/**
* Returns if stepDown and stepUp methods apply for the current type.
@@ -1075,11 +1080,7 @@ protected:
/**
* Returns if valueAsNumber attribute applies for the current type.
*/
- bool DoesValueAsNumberApply() const
- {
- // TODO: this is temporary until bug 888331 is fixed.
- return DoesMinMaxApply() && mType != NS_FORM_INPUT_DATETIME_LOCAL;
- }
+ bool DoesValueAsNumberApply() const { return DoesMinMaxApply(); }
/**
* Returns if autocomplete attribute applies for the current type.
@@ -1287,6 +1288,7 @@ protected:
* https://html.spec.whatwg.org/multipage/infrastructure.html#valid-normalised-local-date-and-time-string
*/
void NormalizeDateTimeLocal(nsAString& aValue) const;
+
/**
* This methods returns the number of days since epoch for a given year and
* week.
@@ -1318,6 +1320,13 @@ protected:
uint32_t MaximumWeekInYear(uint32_t aYear) const;
/**
+ * This method converts aValue (milliseconds within a day) to hours, minutes,
+ * seconds and milliseconds.
+ */
+ bool GetTimeFromMs(double aValue, uint16_t* aHours, uint16_t* aMinutes,
+ uint16_t* aSeconds, uint16_t* aMilliseconds) const;
+
+ /**
* This methods returns true if it's a leap year.
*/
bool IsLeapYear(uint32_t aYear) const;
@@ -1442,7 +1451,6 @@ protected:
};
nsresult InitFilePicker(FilePickerType aType);
nsresult InitColorPicker();
- nsresult InitDatePicker();
/**
* Use this function before trying to open a picker.
@@ -1632,9 +1640,73 @@ private:
return IsSingleLineTextControl(false, aType) ||
aType == NS_FORM_INPUT_RANGE ||
aType == NS_FORM_INPUT_NUMBER ||
- aType == NS_FORM_INPUT_TIME;
+ aType == NS_FORM_INPUT_TIME ||
+ aType == NS_FORM_INPUT_DATE;
}
+ /**
+ * Checks if aDateTimeInputType should be supported based on "dom.forms.datetime",
+ * and "dom.experimental_forms".
+ */
+ static bool
+ IsDateTimeTypeSupported(uint8_t aDateTimeInputType);
+
+ /**
+ * Checks preference "dom.webkitBlink.dirPicker.enabled" to determine if
+ * webkitdirectory should be supported.
+ */
+ static bool
+ IsWebkitDirPickerEnabled();
+
+ /**
+ * Checks preference "dom.webkitBlink.filesystem.enabled" to determine if
+ * webkitEntries should be supported.
+ */
+ static bool
+ IsWebkitFileSystemEnabled();
+
+ /**
+ * Checks preference "dom.input.dirpicker" to determine if file and directory
+ * entries API should be supported.
+ */
+ static bool
+ IsDirPickerEnabled();
+
+ /**
+ * Checks preference "dom.experimental_forms" to determine if experimental
+ * implementation of input element should be enabled.
+ */
+ static bool
+ IsExperimentalFormsEnabled();
+
+ /**
+ * Checks preference "dom.forms.datetime" to determine if input date and time
+ * should be supported.
+ */
+ static bool
+ IsInputDateTimeEnabled();
+
+ /**
+ * Checks preference "dom.forms.datetime.others" to determine if input week,
+ * month and datetime-local should be supported.
+ */
+ static bool
+ IsInputDateTimeOthersEnabled();
+
+ /**
+ * Checks preference "dom.forms.number" to determine if input type=number
+ * should be supported.
+ */
+ static bool
+ IsInputNumberEnabled();
+
+ /**
+ * Checks preference "dom.forms.color" to determine if date/time related
+ * types should be supported.
+ */
+ static bool
+ IsInputColorEnabled();
+
struct nsFilePickerFilter {
nsFilePickerFilter()
: mFilterMask(0) {}
diff --git a/dom/html/nsIFormControl.h b/dom/html/nsIFormControl.h
index aaa92146c..e07f7c829 100644
--- a/dom/html/nsIFormControl.h
+++ b/dom/html/nsIFormControl.h
@@ -270,8 +270,8 @@ nsIFormControl::IsSingleLineTextControl(bool aExcludePassword, uint32_t aType)
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
// On Android/B2G, date/time input appears as a normal text box.
aType == NS_FORM_INPUT_TIME ||
-#endif
aType == NS_FORM_INPUT_DATE ||
+#endif
aType == NS_FORM_INPUT_MONTH ||
aType == NS_FORM_INPUT_WEEK ||
aType == NS_FORM_INPUT_DATETIME_LOCAL ||
diff --git a/dom/html/test/forms/mochitest.ini b/dom/html/test/forms/mochitest.ini
index 35955b189..199e4baf8 100644
--- a/dom/html/test/forms/mochitest.ini
+++ b/dom/html/test/forms/mochitest.ini
@@ -30,8 +30,14 @@ skip-if = os == "android" # up/down arrow keys not supported on android
skip-if = android_version == '18' # Android, bug 1147974
[test_input_color_picker_update.html]
skip-if = android_version == '18' # Android, bug 1147974
+[test_input_date_key_events.html]
+skip-if = os == "android"
+[test_input_datetime_input_change_events.html]
+skip-if = os == "android"
[test_input_datetime_focus_blur.html]
skip-if = os == "android"
+[test_input_datetime_focus_blur_events.html]
+skip-if = os == "android"
[test_input_datetime_tabindex.html]
skip-if = os == "android"
[test_input_defaultValue.html]
@@ -61,8 +67,6 @@ skip-if = os == "android"
[test_input_textarea_set_value_no_scroll.html]
[test_input_time_key_events.html]
skip-if = os == "android"
-[test_input_time_focus_blur_events.html]
-skip-if = os == "android"
[test_input_types_pref.html]
[test_input_typing_sanitization.html]
[test_input_untrusted_key_events.html]
diff --git a/dom/html/test/forms/test_input_date_key_events.html b/dom/html/test/forms/test_input_date_key_events.html
new file mode 100644
index 000000000..f502d6a4d
--- /dev/null
+++ b/dom/html/test/forms/test_input_date_key_events.html
@@ -0,0 +1,228 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1286182
+-->
+<head>
+ <title>Test key events for time control</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1286182">Mozilla Bug 1286182</a>
+<p id="display"></p>
+<div id="content">
+ <input id="input" type="date">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+// Turn off Spatial Navigation because it hijacks arrow keydown events:
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, function() {
+ test();
+ SimpleTest.finish();
+ });
+});
+
+var testData = [
+ /**
+ * keys: keys to send to the input element.
+ * initialVal: initial value set to the input element.
+ * expectedVal: expected value of the input element after sending the keys.
+ */
+ {
+ // Type 11222016, default order is month, day, year.
+ keys: ["11222016"],
+ initialVal: "",
+ expectedVal: "2016-11-22"
+ },
+ {
+ // Type 3 in the month field will automatically advance to the day field,
+ // then type 5 in the day field will automatically advance to the year
+ // field.
+ keys: ["352016"],
+ initialVal: "",
+ expectedVal: "2016-03-05"
+ },
+ {
+ // Type 13 in the month field will set it to the maximum month, which is
+ // 12.
+ keys: ["13012016"],
+ initialVal: "",
+ expectedVal: "2016-12-01"
+ },
+ {
+ // Type 00 in the month field will set it to the minimum month, which is 1.
+ keys: ["00012016"],
+ initialVal: "",
+ expectedVal: "2016-01-01"
+ },
+ {
+ // Type 33 in the day field will set it to the maximum day, which is 31.
+ keys: ["12332016"],
+ initialVal: "",
+ expectedVal: "2016-12-31"
+ },
+ {
+ // Type 00 in the day field will set it to the minimum day, which is 1.
+ keys: ["12002016"],
+ initialVal: "",
+ expectedVal: "2016-12-01"
+ },
+ {
+ // Type 275769 in the year field will set it to the maximum year, which is
+ // 275760.
+ keys: ["0101275769"],
+ initialVal: "",
+ expectedVal: "275760-01-01"
+ },
+ {
+ // Type 000000 in the year field will set it to the minimum year, which is
+ // 0001.
+ keys: ["0101000000"],
+ initialVal: "",
+ expectedVal: "0001-01-01"
+ },
+ {
+ // Advance to year field and decrement.
+ keys: ["VK_TAB", "VK_TAB", "VK_DOWN"],
+ initialVal: "2016-11-25",
+ expectedVal: "2015-11-25"
+ },
+ {
+ // Right key should do the same thing as TAB key.
+ keys: ["VK_RIGHT", "VK_RIGHT", "VK_DOWN"],
+ initialVal: "2016-11-25",
+ expectedVal: "2015-11-25"
+ },
+ {
+ // Advance to day field then back to month field and decrement.
+ keys: ["VK_RIGHT", "VK_LEFT", "VK_DOWN"],
+ initialVal: "2000-05-01",
+ expectedVal: "2000-04-01"
+ },
+ {
+ // Focus starts on the first field, month in this case, and increment.
+ keys: ["VK_UP"],
+ initialVal: "2000-03-01",
+ expectedVal: "2000-04-01"
+ },
+ {
+ // Advance to day field and decrement.
+ keys: ["VK_TAB", "VK_DOWN"],
+ initialVal: "1234-01-01",
+ expectedVal: "1234-01-31"
+ },
+ {
+ // Advance to day field and increment.
+ keys: ["VK_TAB", "VK_UP"],
+ initialVal: "1234-01-01",
+ expectedVal: "1234-01-02"
+ },
+ {
+ // PageUp on month field increments month by 3.
+ keys: ["VK_PAGE_UP"],
+ initialVal: "1999-01-01",
+ expectedVal: "1999-04-01"
+ },
+ {
+ // PageDown on month field decrements month by 3.
+ keys: ["VK_PAGE_DOWN"],
+ initialVal: "1999-01-01",
+ expectedVal: "1999-10-01"
+ },
+ {
+ // PageUp on day field increments day by 7.
+ keys: ["VK_TAB", "VK_PAGE_UP"],
+ initialVal: "1999-01-01",
+ expectedVal: "1999-01-08"
+ },
+ {
+ // PageDown on day field decrements day by 7.
+ keys: ["VK_TAB", "VK_PAGE_DOWN"],
+ initialVal: "1999-01-01",
+ expectedVal: "1999-01-25"
+ },
+ {
+ // PageUp on year field increments year by 10.
+ keys: ["VK_TAB", "VK_TAB", "VK_PAGE_UP"],
+ initialVal: "1999-01-01",
+ expectedVal: "2009-01-01"
+ },
+ {
+ // PageDown on year field decrements year by 10.
+ keys: ["VK_TAB", "VK_TAB", "VK_PAGE_DOWN"],
+ initialVal: "1999-01-01",
+ expectedVal: "1989-01-01"
+ },
+ {
+ // Home key on month field sets it to the minimum month, which is 01.
+ keys: ["VK_HOME"],
+ initialVal: "2016-06-01",
+ expectedVal: "2016-01-01"
+ },
+ {
+ // End key on month field sets it to the maximum month, which is 12.
+ keys: ["VK_END"],
+ initialVal: "2016-06-01",
+ expectedVal: "2016-12-01"
+ },
+ {
+ // Home key on day field sets it to the minimum day, which is 01.
+ keys: ["VK_TAB", "VK_HOME"],
+ initialVal: "2016-01-10",
+ expectedVal: "2016-01-01"
+ },
+ {
+ // End key on day field sets it to the maximum day, which is 31.
+ keys: ["VK_TAB", "VK_END"],
+ initialVal: "2016-01-10",
+ expectedVal: "2016-01-31"
+ },
+ {
+ // Home key should have no effect on year field.
+ keys: ["VK_TAB", "VK_TAB", "VK_HOME"],
+ initialVal: "2016-01-01",
+ expectedVal: "2016-01-01"
+ },
+ {
+ // End key should have no effect on year field.
+ keys: ["VK_TAB", "VK_TAB", "VK_END"],
+ initialVal: "2016-01-01",
+ expectedVal: "2016-01-01"
+ },
+];
+
+function sendKeys(aKeys) {
+ for (let i = 0; i < aKeys.length; i++) {
+ let key = aKeys[i];
+ if (key.startsWith("VK")) {
+ synthesizeKey(key, {});
+ } else {
+ sendString(key);
+ }
+ }
+}
+
+function test() {
+ var elem = document.getElementById("input");
+
+ for (let { keys, initialVal, expectedVal } of testData) {
+ elem.focus();
+ elem.value = initialVal;
+ sendKeys(keys);
+ elem.blur();
+ is(elem.value, expectedVal,
+ "Test with " + keys + ", result should be " + expectedVal);
+ elem.value = "";
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_datetime_focus_blur.html b/dom/html/test/forms/test_input_datetime_focus_blur.html
index 5b8d95b25..85f7b4bb4 100644
--- a/dom/html/test/forms/test_input_datetime_focus_blur.html
+++ b/dom/html/test/forms/test_input_datetime_focus_blur.html
@@ -4,7 +4,7 @@
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
-->
<head>
- <title>Test focus/blur behaviour for &lt;input type='time'&gt;</title>
+ <title>Test focus/blur behaviour for date/time input types</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
@@ -12,7 +12,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1288591">Mozilla Bug 1288591</a>
<p id="display"></p>
<div id="content">
- <input id="input" type="time">
+ <input id="input_time" type="time">
+ <input id="input_date" type="date">
</div>
<pre id="test">
<script type="application/javascript">
@@ -30,15 +31,15 @@ SimpleTest.waitForFocus(function() {
SimpleTest.finish();
});
-function test() {
- let time = document.getElementById("input");
- time.focus();
+function testFocusBlur(type) {
+ let input = document.getElementById("input_" + type);
+ input.focus();
- // The active element returns the input type=time.
+ // The active element returns the date/time input element.
let activeElement = document.activeElement;
- is(activeElement, time, "activeElement should be the time element");
+ is(activeElement, input, "activeElement should be the date/time input element");
is(activeElement.localName, "input", "activeElement should be an input element");
- is(activeElement.type, "time", "activeElement should be of type time");
+ is(activeElement.type, type, "activeElement should be of type " + type);
// Use FocusManager to check that the actual focus is on the anonymous
// text control.
@@ -48,10 +49,17 @@ function test() {
is(focusedElement.localName, "input", "focusedElement should be an input element");
is(focusedElement.type, "text", "focusedElement should be of type text");
- time.blur();
- isnot(document.activeElement, time, "activeElement should no longer be the time element");
+ input.blur();
+ isnot(document.activeElement, input, "activeElement should no longer be the datetime input element");
}
+function test() {
+ let inputTypes = ["time", "date"];
+
+ for (let i = 0; i < inputTypes.length; i++) {
+ testFocusBlur(inputTypes[i]);
+ }
+}
</script>
</pre>
</body>
diff --git a/dom/html/test/forms/test_input_datetime_focus_blur_events.html b/dom/html/test/forms/test_input_datetime_focus_blur_events.html
new file mode 100644
index 000000000..873dda627
--- /dev/null
+++ b/dom/html/test/forms/test_input_datetime_focus_blur_events.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1301306
+-->
+<head>
+<title>Test for Bug 1301306</title>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1301306">Mozilla Bug 722599</a>
+<p id="display"></p>
+<div id="content">
+<input type="time" id="input_time" onfocus="++focusEvents[0]"
+ onblur="++blurEvents[0]" onfocusin="++focusInEvents[0]"
+ onfocusout="++focusOutEvents[0]">
+<input type="date" id="input_date" onfocus="++focusEvents[1]"
+ onblur="++blurEvents[1]" onfocusin="++focusInEvents[1]"
+ onfocusout="++focusOutEvents[1]">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * Test for Bug 1301306.
+ * This test checks that when moving inside the time input element, e.g. jumping
+ * through the inner text boxes, does not fire extra focus/blur events.
+ **/
+
+var inputTypes = ["time", "date"];
+var focusEvents = [0, 0];
+var focusInEvents = [0, 0];
+var focusOutEvents = [0, 0];
+var blurEvents = [0, 0];
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test();
+ SimpleTest.finish();
+});
+
+function test() {
+ for (var i = 0; i < inputTypes.length; i++) {
+ var input = document.getElementById("input_" + inputTypes[i]);
+
+ input.focus();
+ is(focusEvents[i], 1, inputTypes[i] + " input element should have dispatched focus event.");
+ is(focusInEvents[i], 1, inputTypes[i] + " input element should have dispatched focusin event.");
+ is(focusOutEvents[i], 0, inputTypes[i] + " input element should not have dispatched focusout event.");
+ is(blurEvents[i], 0, inputTypes[i] + " input element should not have dispatched blur event.");
+
+ // Move around inside the input element's input box.
+ synthesizeKey("VK_TAB", {});
+ is(focusEvents[i], 1, inputTypes[i] + " input element should not have dispatched focus event.");
+ is(focusInEvents[i], 1, inputTypes[i] + " input element should not have dispatched focusin event.");
+ is(focusOutEvents[i], 0, inputTypes[i] + " input element should not have dispatched focusout event.");
+ is(blurEvents[i], 0, inputTypes[i] + " time input element should not have dispatched blur event.");
+
+ synthesizeKey("VK_RIGHT", {});
+ is(focusEvents[i], 1, inputTypes[i] + " input element should not have dispatched focus event.");
+ is(focusInEvents[i], 1, inputTypes[i] + " input element should not have dispatched focusin event.");
+ is(focusOutEvents[i], 0, inputTypes[i] + " input element should not have dispatched focusout event.");
+ is(blurEvents[i], 0, inputTypes[i] + " input element should not have dispatched blur event.");
+
+ synthesizeKey("VK_LEFT", {});
+ is(focusEvents[i], 1,inputTypes[i] + " input element should not have dispatched focus event.");
+ is(focusInEvents[i], 1, inputTypes[i] + " input element should not have dispatched focusin event.");
+ is(focusOutEvents[i], 0, inputTypes[i] + " input element should not have dispatched focusout event.");
+ is(blurEvents[i], 0, inputTypes[i] + " input element should not have dispatched blur event.");
+
+ synthesizeKey("VK_RIGHT", {});
+ is(focusEvents[i], 1, inputTypes[i] + " input element should not have dispatched focus event.");
+ is(focusInEvents[i], 1, inputTypes[i] + " input element should not have dispatched focusin event.");
+ is(focusOutEvents[i], 0, inputTypes[i] + " input element should not have dispatched focusout event.");
+ is(blurEvents[i], 0, inputTypes[i] + " input element should not have dispatched blur event.");
+
+ input.blur();
+ is(focusEvents[i], 1, inputTypes[i] + " input element should not have dispatched focus event.");
+ is(focusInEvents[i], 1, inputTypes[i] + " input element should not have dispatched focusin event.");
+ is(focusOutEvents[i], 1, inputTypes[i] + " input element should have dispatched focusout event.");
+ is(blurEvents[i], 1, inputTypes[i] + " input element should have dispatched blur event.");
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_datetime_input_change_events.html b/dom/html/test/forms/test_input_datetime_input_change_events.html
new file mode 100644
index 000000000..e636995d3
--- /dev/null
+++ b/dom/html/test/forms/test_input_datetime_input_change_events.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1370858
+-->
+<head>
+<title>Test for Bug 1370858</title>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1370858">Mozilla Bug 722599</a>
+<p id="display"></p>
+<div id="content">
+<input type="time" id="input_time" onchange="++changeEvents[0]"
+ oninput="++inputEvents[0]">
+<input type="date" id="input_date" onchange="++changeEvents[1]"
+ oninput="++inputEvents[1]">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * Test for Bug 1370858.
+ * Test that change and input events are (not) fired for date/time inputs.
+ **/
+
+var inputTypes = ["time", "date"];
+var changeEvents = [0, 0];
+var inputEvents = [0, 0];
+var values = ["10:30", "2017-06-08"];
+var expectedValues = [["09:30", "01:30"], ["2017-05-08", "2017-01-08"]];
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test();
+ SimpleTest.finish();
+});
+
+function test() {
+ for (var i = 0; i < inputTypes.length; i++) {
+ var input = document.getElementById("input_" + inputTypes[i]);
+ var inputRect = input.getBoundingClientRect();
+
+ // Points over the input's reset button
+ var resetButton_X = inputRect.width - 15;
+ var resetButton_Y = inputRect.height / 2;
+
+ is(changeEvents[i], 0, "Number of change events should be 0 at start.");
+ is(inputEvents[i], 0, "Number of input events should be 0 at start.");
+
+ // Test that change and input events are not dispatched setting .value by
+ // script.
+ input.value = values[i];
+ is(input.value, values[i], "Check that value was set correctly (0).");
+ is(changeEvents[i], 0, "Change event should not have dispatched (0).");
+ is(inputEvents[i], 0, "Input event should not have dispatched (0).");
+
+ // Test that change and input events are fired when changing the value using
+ // up/down keys.
+ input.focus();
+ synthesizeKey("VK_DOWN", {});
+ is(input.value, expectedValues[i][0], "Check that value was set correctly (1).");
+ is(changeEvents[i], 1, "Change event should be dispatched (1).");
+ is(inputEvents[i], 1, "Input event should ne dispatched (1).");
+
+ // Test that change and input events are fired when changing the value with
+ // the keyboard.
+ synthesizeKey("0", {});
+ synthesizeKey("1", {});
+ is(input.value, expectedValues[i][1], "Check that value was set correctly (2).");
+ is(changeEvents[i], 2, "Change event should be dispatched (2).");
+ is(inputEvents[i], 2, "Input event should be dispatched (2).");
+
+ // Test that change and input events are fired when clearing the value using
+ // the reset button.
+ synthesizeMouse(input, resetButton_X, resetButton_Y, {});
+ is(input.value, "", "Check that value was set correctly (3).");
+ is(changeEvents[i], 3, "Change event should be dispatched (3).");
+ is(inputEvents[i], 3, "Input event should be dispatched (3).");
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_datetime_tabindex.html b/dom/html/test/forms/test_input_datetime_tabindex.html
index fb7c9b2f1..8023ccf9b 100644
--- a/dom/html/test/forms/test_input_datetime_tabindex.html
+++ b/dom/html/test/forms/test_input_datetime_tabindex.html
@@ -4,7 +4,7 @@
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
-->
<head>
- <title>Test tabindex attribute for &lt;input type='time'&gt;</title>
+ <title>Test tabindex attribute for date/time input types</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
@@ -16,13 +16,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
<input id="time1" type="time" tabindex="0">
<input id="time2" type="time" tabindex="-1">
<input id="time3" type="time" tabindex="0">
+ <input id="date1" type="date" tabindex="0">
+ <input id="date2" type="date" tabindex="-1">
+ <input id="date3" type="date" tabindex="0">
</div>
<pre id="test">
<script type="application/javascript">
/**
* Test for Bug 1288591.
- * This test checks whether date/time input types' tabindex attribute works
+ * This test checks whether date/time input types tabindex attribute works
* correctly.
**/
SimpleTest.waitForExplicitFinish();
@@ -31,41 +34,49 @@ SimpleTest.waitForFocus(function() {
SimpleTest.finish();
});
-function test() {
- let time1 = document.getElementById("time1");
- let time2 = document.getElementById("time2");
- let time3 = document.getElementById("time3");
+function testTabindex(type) {
+ let input1 = document.getElementById(type + "1");
+ let input2 = document.getElementById(type + "2");
+ let input3 = document.getElementById(type + "3");
- time1.focus();
- is(document.activeElement, time1,
+ input1.focus();
+ is(document.activeElement, input1,
"input element with tabindex=0 is focusable");
- // Advance to time1 minute field
+ // Advance to next inner field
synthesizeKey("VK_TAB", {});
- is(document.activeElement, time1,
+ is(document.activeElement, input1,
"input element with tabindex=0 is tabbable");
- // Advance to time1 AM/PM field
+ // Advance to next inner field
synthesizeKey("VK_TAB", {});
- is(document.activeElement, time1,
+ is(document.activeElement, input1,
"input element with tabindex=0 is tabbable");
// Advance to next element
synthesizeKey("VK_TAB", {});
- is(document.activeElement, time3,
+ is(document.activeElement, input3,
"input element with tabindex=-1 is not tabbable");
- time2.focus();
- is(document.activeElement, time2,
+ input2.focus();
+ is(document.activeElement, input2,
"input element with tabindex=-1 is still focusable");
// Changing the tabindex attribute dynamically.
- time3.setAttribute("tabindex", "-1");
- synthesizeKey("VK_TAB", {}); // need only one TAB since time2 is not tabbable
- isnot(document.activeElement, time3,
+ input3.setAttribute("tabindex", "-1");
+ synthesizeKey("VK_TAB", {}); // need only one TAB since input2 is not tabbable
+ isnot(document.activeElement, input3,
"element with tabindex changed to -1 should not be tabbable");
}
+function test() {
+ let inputTypes = ["time", "date"];
+
+ for (let i = 0; i < inputTypes.length; i++) {
+ testTabindex(inputTypes[i]);
+ }
+}
+
</script>
</pre>
</body>
diff --git a/dom/html/test/forms/test_input_time_focus_blur_events.html b/dom/html/test/forms/test_input_time_focus_blur_events.html
deleted file mode 100644
index 483741477..000000000
--- a/dom/html/test/forms/test_input_time_focus_blur_events.html
+++ /dev/null
@@ -1,82 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1301306
--->
-<head>
-<title>Test for Bug 1301306</title>
-<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1301306">Mozilla Bug 722599</a>
-<p id="display"></p>
-<div id="content">
-<input type="time" id="input_time" onfocus="++focusEvent" onblur="++blurEvent"
- onfocusin="++focusInEvent" onfocusout="++focusOutEvent">
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/**
- * Test for Bug 1301306.
- * This test checks that when moving inside the time input element, e.g. jumping
- * through the inner text boxes, does not fire extra focus/blur events.
- **/
-
-var focusEvent = 0;
-var focusInEvent = 0;
-var focusOutEvent = 0;
-var blurEvent = 0;
-
-SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
- test();
- SimpleTest.finish();
-});
-
-function test() {
- var time = document.getElementById("input_time");
- time.focus();
- is(focusEvent, 1, "time input element should have dispatched focus event.");
- is(focusInEvent, 1, "time input element should have dispatched focusin event.");
- is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
- is(blurEvent, 0, "time input element should not have dispatched blur event.");
-
- // Move around inside the input element's input box.
- synthesizeKey("VK_TAB", {});
- is(focusEvent, 1, "time input element should not have dispatched focus event.");
- is(focusInEvent, 1, "time input element should have dispatched focusin event.");
- is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
- is(blurEvent, 0, "time input element should not have dispatched blur event.");
-
- synthesizeKey("VK_RIGHT", {});
- is(focusEvent, 1, "time input element should not have dispatched focus event.");
- is(focusInEvent, 1, "time input element should have dispatched focusin event.");
- is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
- is(blurEvent, 0, "time input element should not have dispatched blur event.");
-
- synthesizeKey("VK_LEFT", {});
- is(focusEvent, 1, "time input element should not have dispatched focus event.");
- is(focusInEvent, 1, "time input element should have dispatched focusin event.");
- is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
- is(blurEvent, 0, "time input element should not have dispatched blur event.");
-
- synthesizeKey("VK_RIGHT", {});
- is(focusEvent, 1, "time input element should not have dispatched focus event.");
- is(focusInEvent, 1, "time input element should have dispatched focusin event.");
- is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
- is(blurEvent, 0, "time input element should not have dispatched blur event.");
-
- time.blur();
- is(focusEvent, 1, "time input element should not have dispatched focus event.");
- is(focusInEvent, 1, "time input element should have dispatched focusin event.");
- is(focusOutEvent, 1, "time input element should not have dispatched focusout event.");
- is(blurEvent, 1, "time input element should have dispatched blur event.");
-}
-
-</script>
-</pre>
-</body>
-</html>
diff --git a/dom/html/test/forms/test_input_types_pref.html b/dom/html/test/forms/test_input_types_pref.html
index 243836f34..5279d6a2a 100644
--- a/dom/html/test/forms/test_input_types_pref.html
+++ b/dom/html/test/forms/test_input_types_pref.html
@@ -37,47 +37,63 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=764481
inputType: "color",
expectedType: "color"
}, {
- prefs: [["dom.experimental_forms", false], ["dom.forms.datepicker", false],
- ["dom.forms.datetime", false]],
+ prefs: [["dom.experimental_forms", false], ["dom.forms.datetime", false]],
inputType: "date",
expectedType: "text"
}, {
- prefs: [["dom.experimental_forms", true], ["dom.forms.datepicker", false],
- ["dom.forms.datetime", false]],
+ prefs: [["dom.experimental_forms", true], ["dom.forms.datetime", false]],
inputType: "date",
expectedType: "date"
}, {
- prefs: [["dom.experimental_forms", false], ["dom.forms.datepicker", true],
- ["dom.forms.datetime", false]],
+ prefs: [["dom.experimental_forms", false], ["dom.forms.datetime", true]],
inputType: "date",
expectedType: "date"
}, {
- prefs: [["dom.experimental_forms", false], ["dom.forms.datepicker", false],
- ["dom.forms.datetime", true]],
- inputType: "date",
- expectedType: "date"
+ prefs: [["dom.experimental_forms", false], ["dom.forms.datetime", false]],
+ inputType: "time",
+ expectedType: "text"
+ }, {
+ prefs: [["dom.experimental_forms", true], ["dom.forms.datetime", false]],
+ inputType: "time",
+ expectedType: "time"
+ }, {
+ prefs: [["dom.experimental_forms", false], ["dom.forms.datetime", true]],
+ inputType: "time",
+ expectedType: "time"
+ }, {
+ prefs: [["dom.forms.datetime", false], ["dom.forms.datetime.others", false]],
+ inputType: "month",
+ expectedType: "text"
}, {
- prefs: [["dom.forms.datetime", false]],
+ prefs: [["dom.forms.datetime", true], ["dom.forms.datetime.others", false]],
inputType: "month",
expectedType: "text"
}, {
- prefs: [["dom.forms.datetime", true]],
+ prefs: [["dom.forms.datetime", false], ["dom.forms.datetime.others", true]],
inputType: "month",
expectedType: "month"
}, {
- prefs: [["dom.forms.datetime", false]],
+ prefs: [["dom.forms.datetime", false], ["dom.forms.datetime.others", false]],
+ inputType: "week",
+ expectedType: "text"
+ }, {
+ prefs: [["dom.forms.datetime", true], ["dom.forms.datetime.others", false]],
inputType: "week",
expectedType: "text"
}, {
- prefs: [["dom.forms.datetime", true]],
+ prefs: [["dom.forms.datetime", false], ["dom.forms.datetime.others", true]],
inputType: "week",
expectedType: "week"
}, {
- prefs: [["dom.forms.datetime", false]],
+ prefs: [["dom.forms.datetime", false], ["dom.forms.datetime.others", false]],
+ inputType: "datetime-local",
+ expectedType: "text"
+ }, {
+ prefs: [["dom.forms.datetime", true], ["dom.forms.datetime.others", false]],
inputType: "datetime-local",
expectedType: "text"
}, {
- prefs: [["dom.forms.datetime", true]],
+ prefs: [["dom.forms.datetime", false], ["dom.forms.datetime.others", true]],
inputType: "datetime-local",
expectedType: "datetime-local"
}
diff --git a/dom/html/test/forms/test_input_typing_sanitization.html b/dom/html/test/forms/test_input_typing_sanitization.html
index 0896f19df..eee300b33 100644
--- a/dom/html/test/forms/test_input_typing_sanitization.html
+++ b/dom/html/test/forms/test_input_typing_sanitization.html
@@ -26,6 +26,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=765772
* This test checks that when a user types in some input types, it will not be
* in a state where the value will be un-sanitized and usable (by a script).
*/
+const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
var input = document.getElementById('i');
var form = document.getElementById('f');
@@ -143,6 +144,7 @@ function runTest()
]
},
{
+ mobileOnly: true,
type: 'date',
validData: [
'0001-01-01',
@@ -161,6 +163,28 @@ function runTest()
]
},
{
+ mobileOnly: true,
+ type: 'time',
+ validData: [
+ '00:00',
+ '09:09:00',
+ '08:23:23.1',
+ '21:43:56.12',
+ '23:12:45.100',
+ ],
+ invalidData: [
+ '00:',
+ '00:00:',
+ '25:00',
+ '-00:00',
+ '00:00:00.',
+ '00:60',
+ '10:58:99',
+ ':19:10',
+ '23:08:09.1012',
+ ]
+ },
+ {
type: 'month',
validData: [
'0001-01',
@@ -218,6 +242,10 @@ function runTest()
for (test of data) {
gCurrentTest = test;
+ if (gCurrentTest.mobileOnly && isDesktop) {
+ continue;
+ }
+
input.type = test.type;
gValidData = test.validData;
gInvalidData = test.invalidData;
diff --git a/dom/html/test/forms/test_max_attribute.html b/dom/html/test/forms/test_max_attribute.html
index 4007cfad6..091ad321b 100644
--- a/dom/html/test/forms/test_max_attribute.html
+++ b/dom/html/test/forms/test_max_attribute.html
@@ -31,8 +31,7 @@ var data = [
{ type: 'month', apply: true },
{ type: 'week', apply: true },
{ type: 'time', apply: true },
- // TODO: temporary set to false until bug 888331 is fixed.
- { type: 'datetime-local', apply: false },
+ { type: 'datetime-local', apply: true },
{ type: 'number', apply: true },
{ type: 'range', apply: true },
{ type: 'color', apply: false },
@@ -71,7 +70,8 @@ function checkValidity(aElement, aValidity, aApply, aRangeApply)
"element overflow status should be " + !aValidity);
var overflowMsg =
(aElement.type == "date" || aElement.type == "time" ||
- aElement.type == "month" || aElement.type == "week") ?
+ aElement.type == "month" || aElement.type == "week" ||
+ aElement.type == "datetime-local") ?
("Please select a value that is no later than " + aElement.max + ".") :
("Please select a value that is no more than " + aElement.max + ".");
is(aElement.validationMessage,
@@ -148,7 +148,7 @@ for (var test of data) {
input.max = '2016-W39';
break;
case 'datetime-local':
- // TODO: this is temporary until bug 888331 is fixed.
+ input.max = '2016-12-31T23:59:59';
break;
default:
ok(false, 'please, add a case for this new type (' + input.type + ')');
@@ -421,7 +421,44 @@ for (var test of data) {
break;
case 'datetime-local':
- // TODO: this is temporary until bug 888331 is fixed.
+ input.value = '2016-01-01T12:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2016-12-31T23:59:59';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2016-12-31T23:59:59.123';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '2017-01-01T10:00';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '2017-01-01T10:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2017-01-01T10:00:30';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '1000-01-01T12:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2100-01-01T12:00';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '0050-12-31T23:59:59.999';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '0050-12-31T23:59:59';
+ checkValidity(input, true, apply, apply);
+
+ input.max = '';
+ checkValidity(input, true, apply, false);
+
+ input.max = 'foo';
+ checkValidity(input, true, apply, false);
break;
}
diff --git a/dom/html/test/forms/test_min_attribute.html b/dom/html/test/forms/test_min_attribute.html
index 1258babec..22f21de39 100644
--- a/dom/html/test/forms/test_min_attribute.html
+++ b/dom/html/test/forms/test_min_attribute.html
@@ -31,8 +31,7 @@ var data = [
{ type: 'month', apply: true },
{ type: 'week', apply: true },
{ type: 'time', apply: true },
- // TODO: temporary set to false until bug 888331 is fixed.
- { type: 'datetime-local', apply: false },
+ { type: 'datetime-local', apply: true },
{ type: 'number', apply: true },
{ type: 'range', apply: true },
{ type: 'color', apply: false },
@@ -71,7 +70,8 @@ function checkValidity(aElement, aValidity, aApply, aRangeApply)
"element underflow status should be " + !aValidity);
var underflowMsg =
(aElement.type == "date" || aElement.type == "time" ||
- aElement.type == "month" || aElement.type == "week") ?
+ aElement.type == "month" || aElement.type == "week" ||
+ aElement.type == "datetime-local") ?
("Please select a value that is no earlier than " + aElement.min + ".") :
("Please select a value that is no less than " + aElement.min + ".");
is(aElement.validationMessage,
@@ -146,10 +146,10 @@ for (var test of data) {
input.min = '2016-06';
break;
case 'week':
- input.min = "2016-W39";
+ input.min = '2016-W39';
break;
case 'datetime-local':
- // TODO: this is temporary until bug 888331 is fixed.
+ input.min = '2017-01-01T00:00';
break;
default:
ok(false, 'please, add a case for this new type (' + input.type + ')');
@@ -420,7 +420,44 @@ for (var test of data) {
checkValidity(input, true, apply, false);
break;
case 'datetime-local':
- // TODO: this is temporary until bug 888331 is fixed.
+ input.value = '2017-12-31T23:59';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2017-01-01T00:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2017-01-01T00:00:00.123';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2016-12-31T23:59';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '2016-01-01T00:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2015-12-31T23:59';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '1000-01-01T00:00';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '10000-01-01T00:00';
+ checkValidity(input, true, apply, apply);
+
+ input.min = '0010-01-01T12:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '0010-01-01T10:00';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '';
+ checkValidity(input, true, apply, false);
+
+ input.min = 'foo';
+ checkValidity(input, true, apply, false);
break;
default:
ok(false, 'write tests for ' + input.type);
diff --git a/dom/html/test/forms/test_step_attribute.html b/dom/html/test/forms/test_step_attribute.html
index 31277860c..a14afa461 100644
--- a/dom/html/test/forms/test_step_attribute.html
+++ b/dom/html/test/forms/test_step_attribute.html
@@ -31,8 +31,7 @@ var data = [
{ type: 'month', apply: true },
{ type: 'week', apply: true },
{ type: 'time', apply: true },
- // TODO: temporary set to false until bug 888331 is fixed.
- { type: 'datetime-local', apply: false },
+ { type: 'datetime-local', apply: true },
{ type: 'number', apply: true },
{ type: 'range', apply: true },
{ type: 'color', apply: false },
@@ -950,7 +949,104 @@ for (var test of data) {
break;
case 'datetime-local':
- // TODO: this is temporary until bug 888331 is fixed.
+ // When step is invalid, every datetime is valid
+ input.step = 0;
+ input.value = '2017-02-06T12:00';
+ checkValidity(input, true, apply);
+
+ input.step = 'foo';
+ input.value = '1970-01-01T00:00';
+ checkValidity(input, true, apply);
+
+ input.step = '-1';
+ input.value = '1969-12-12 00:10';
+ checkValidity(input, true, apply);
+
+ input.removeAttribute('step');
+ input.value = '1500-01-01T12:00';
+ checkValidity(input, true, apply);
+
+ input.step = 'any';
+ input.value = '1966-12-12T12:00';
+ checkValidity(input, true, apply);
+
+ input.step = 'ANY';
+ input.value = '2017-01-01 12:00';
+ checkValidity(input, true, apply);
+
+ // When min is set to a valid datetime, there is a step base.
+ input.min = '2017-01-01T00:00:00';
+ input.step = '2';
+ input.value = '2017-01-01T00:00:02';
+ checkValidity(input, true, apply);
+
+ input.value = '2017-01-01T00:00:03';
+ checkValidity(input, false, apply,
+ { low: "2017-01-01T00:00:02", high: "2017-01-01T00:00:04" });
+
+ input.min = '2017-01-01T00:00:05';
+ input.value = '2017-01-01T00:00:08';
+ checkValidity(input, false, apply,
+ { low: "2017-01-01T00:00:07", high: "2017-01-01T00:00:09" });
+
+ input.min = '2000-01-01T00:00';
+ input.step = '120';
+ input.value = '2000-01-01T00:02';
+ checkValidity(input, true, apply);
+
+ // Without any step attribute the datetime is valid
+ input.removeAttribute('step');
+ checkValidity(input, true, apply);
+
+ input.min = '1950-01-01T00:00';
+ input.step = '129600'; // 1.5 day
+ input.value = '1950-01-02T00:00';
+ checkValidity(input, false, apply,
+ { low: "1950-01-01T00:00", high: "1950-01-02T12:00" });
+
+ input.step = '259200'; // 3 days
+ input.value = '1950-01-04T12:00';
+ checkValidity(input, false, apply,
+ { low: "1950-01-04T00:00", high: "1950-01-07T00:00" });
+
+ input.value = '1950-01-10T00:00';
+ checkValidity(input, true, apply);
+
+ input.step = '0.5'; // half a second
+ input.value = '1950-01-01T00:00:00.123';
+ checkValidity(input, false, apply,
+ { low: "1950-01-01T00:00", high: "1950-01-01T00:00:00.500" });
+
+ input.value = '2000-01-01T12:30:30.600';
+ checkValidity(input, false, apply,
+ { low: "2000-01-01T12:30:30.500", high: "2000-01-01T12:30:31" });
+
+ input.value = '1950-01-05T00:00:00.500';
+ checkValidity(input, true, apply);
+
+ input.step = '2.1';
+ input.min = '1991-01-01T12:00';
+ input.value = '1991-01-01T12:00';
+ checkValidity(input, true, apply);
+
+ input.value = '1991-01-01T12:00:03';
+ checkValidity(input, false, apply,
+ { low: "1991-01-01T12:00:02.100", high: "1991-01-01T12:00:04.200" });
+
+ input.value = '1991-01-01T12:00:06.3';
+ checkValidity(input, true, apply);
+
+ input.step = '2.1';
+ input.min = '1969-12-20T10:00:05';
+ input.value = '1969-12-20T10:00:05';
+ checkValidity(input, true, apply);
+
+ input.value = '1969-12-20T10:00:08';
+ checkValidity(input, false, apply,
+ { low: "1969-12-20T10:00:07.100", high: "1969-12-20T10:00:09.200" });
+
+ input.value = '1969-12-20T10:00:09.200';
+ checkValidity(input, true, apply);
break;
default:
diff --git a/dom/html/test/forms/test_stepup_stepdown.html b/dom/html/test/forms/test_stepup_stepdown.html
index d96895180..21cde58aa 100644
--- a/dom/html/test/forms/test_stepup_stepdown.html
+++ b/dom/html/test/forms/test_stepup_stepdown.html
@@ -52,13 +52,8 @@ function checkAvailability()
["time", true],
["month", true],
["week", true],
- ["color", false],
- ];
-
- var todoList =
- [
- ["datetime", true],
["datetime-local", true],
+ ["color", false],
];
var element = document.createElement("input");
@@ -82,27 +77,6 @@ function checkAvailability()
}
is(exceptionCaught, !data[1], "stepUp() availability is not correct");
}
-
- for (data of todoList) {
- var exceptionCaught = false;
- element.type = data[0];
- try {
- element.stepDown();
- } catch (e) {
- exceptionCaught = true;
- }
- todo_is(exceptionCaught, !data[1],
- "stepDown() availability is not correct");
-
- exceptionCaught = false;
- try {
- element.stepUp();
- } catch (e) {
- exceptionCaught = true;
- }
- todo_is(exceptionCaught, !data[1],
- "stepUp() availability is not correct");
- }
}
function checkStepDown()
@@ -509,6 +483,80 @@ function checkStepDown()
[ '2016-W01', 'AnY', null, null, 1, null, true ],
[ '2016-W01', 'aNy', null, null, 1, null, true ],
]},
+ { type: 'datetime-local', data: [
+ // Regular case.
+ [ '2017-02-07T09:30', null, null, null, null, '2017-02-07T09:29', false ],
+ // Argument testing.
+ [ '2017-02-07T09:30', null, null, null, 1, '2017-02-07T09:29', false ],
+ [ '2017-02-07T09:30', null, null, null, 5, '2017-02-07T09:25', false ],
+ [ '2017-02-07T09:30', null, null, null, -1, '2017-02-07T09:31', false ],
+ [ '2017-02-07T09:30', null, null, null, 0, '2017-02-07T09:30', false ],
+ // hour/minutes/seconds wrapping.
+ [ '2000-01-01T05:00', null, null, null, null, '2000-01-01T04:59', false ],
+ [ '2000-01-01T05:00:00', 1, null, null, null, '2000-01-01T04:59:59', false ],
+ [ '2000-01-01T05:00:00', 0.1, null, null, null, '2000-01-01T04:59:59.900', false ],
+ [ '2000-01-01T05:00:00', 0.01, null, null, null, '2000-01-01T04:59:59.990', false ],
+ [ '2000-01-01T05:00:00', 0.001, null, null, null, '2000-01-01T04:59:59.999', false ],
+ // month/year wrapping.
+ [ '2012-08-01T12:00', null, null, null, 1440, '2012-07-31T12:00', false ],
+ [ '1969-01-02T12:00', null, null, null, 5760, '1968-12-29T12:00', false ],
+ [ '1969-12-31T00:00', null, null, null, -1440, '1970-01-01T00:00', false ],
+ [ '2012-02-29T00:00', null, null, null, -1440, '2012-03-01T00:00', false ],
+ // stepDown() on '00:00' gives '23:59'.
+ [ '2017-02-07T00:00', null, null, null, 1, '2017-02-06T23:59', false ],
+ [ '2017-02-07T00:00', null, null, null, 3, '2017-02-06T23:57', false ],
+ // Some random step values..
+ [ '2017-02-07T16:07', '0.5', null, null, null, '2017-02-07T16:06:59.500', false ],
+ [ '2017-02-07T16:07', '2', null, null, null, '2017-02-07T16:06:58', false ],
+ [ '2017-02-07T16:07', '0.25', null, null, 4, '2017-02-07T16:06:59', false ],
+ [ '2017-02-07T16:07', '1.1', '2017-02-07T16:00', null, 1, '2017-02-07T16:06:59.100', false ],
+ [ '2017-02-07T16:07', '1.1', '2017-02-07T16:00', null, 2, '2017-02-07T16:06:58', false ],
+ [ '2017-02-07T16:07', '1.1', '2017-02-07T16:00', null, 10, '2017-02-07T16:06:49.200', false ],
+ [ '2017-02-07T16:07', '129600', '2017-02-01T00:00', null, 2, '2017-02-05T12:00', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '2017-02-07T10:15', '0', null, null, null, '2017-02-07T10:14', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '2017-02-07T10:15', '-1', null, null, null, '2017-02-07T10:14', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '2017-02-07T10:15', 'foo', null, null, null, '2017-02-07T10:14', false ],
+ // Min values testing.
+ [ '2012-02-02T17:02', '60', 'foo', null, 2, '2012-02-02T17:00', false ],
+ [ '2012-02-02T17:10', '60', '2012-02-02T17:09', null, null, '2012-02-02T17:09', false ],
+ [ '2012-02-02T17:10', '60', '2012-02-02T17:10', null, null, '2012-02-02T17:10', false ],
+ [ '2012-02-02T17:10', '60', '2012-02-02T17:30', null, 1, '2012-02-02T17:10', false ],
+ [ '2012-02-02T17:10', '180', '2012-02-02T17:05', null, null, '2012-02-02T17:08', false ],
+ [ '2012-02-03T20:05', '86400', '2012-02-02T17:05', null, null, '2012-02-03T17:05', false ],
+ [ '2012-02-03T18:00', '129600', '2012-02-01T00:00', null, null, '2012-02-02T12:00', false ],
+ // Max values testing.
+ [ '2012-02-02T17:15', '60', null, 'foo', null, '2012-02-02T17:14', false ],
+ [ '2012-02-02T17:15', null, null, '2012-02-02T17:20', null, '2012-02-02T17:14', false ],
+ [ '2012-02-02T17:15', null, null, '2012-02-02T17:15', null, '2012-02-02T17:14', false ],
+ [ '2012-02-02T17:15', null, null, '2012-02-02T17:13', 4, '2012-02-02T17:11', false ],
+ [ '2012-02-02T17:15', '120', null, '2012-02-02T17:13', 3, '2012-02-02T17:09', false ],
+ [ '2012-02-03T20:05', '86400', null, '2012-02-03T20:05', null, '2012-02-02T20:05', false ],
+ [ '2012-02-03T18:00', '129600', null, '2012-02-03T20:00', null, '2012-02-02T06:00', false ],
+ // Step mismatch.
+ [ '2017-02-07T17:19', '120', '2017-02-07T17:10', null, null, '2017-02-07T17:18', false ],
+ [ '2017-02-07T17:19', '120', '2017-02-07T17:10', null, 2, '2017-02-07T17:16', false ],
+ [ '2017-02-07T17:19', '120', '2017-02-07T17:18', '2017-02-07T17:25', null, '2017-02-07T17:18', false ],
+ [ '2017-02-07T17:19', '120', null, null, null, '2017-02-07T17:17', false ],
+ [ '2017-02-07T17:19', '180', null, null, null, '2017-02-07T17:16', false ],
+ [ '2017-02-07T17:19', '172800', '2017-02-02T17:19', '2017-02-10T17:19', null, '2017-02-06T17:19', false ],
+ // Clamping.
+ [ '2017-02-07T17:22', null, null, '2017-02-07T17:11', null, '2017-02-07T17:11', false ],
+ [ '2017-02-07T17:22', '120', '2017-02-07T17:20', '2017-02-07T17:22', null, '2017-02-07T17:20', false ],
+ [ '2017-02-07T17:22', '300', '2017-02-07T17:12', '2017-02-07T17:20', 10, '2017-02-07T17:12', false ],
+ [ '2017-02-07T17:22', '300', '2017-02-07T17:18', '2017-02-07T17:20', 2, '2017-02-07T17:18', false ],
+ [ '2017-02-07T17:22', '600', '2017-02-02T17:00', '2017-02-07T17:00', 15, '2017-02-07T15:00', false ],
+ [ '2017-02-07T17:22', '600', '2017-02-02T17:00', '2017-02-07T17:00', 2, '2017-02-07T17:00', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '1969-12-31T23:59', false ],
+ // With step = 'any'.
+ [ '2017-02-07T15:20', 'any', null, null, 1, null, true ],
+ [ '2017-02-07T15:20', 'ANY', null, null, 1, null, true ],
+ [ '2017-02-07T15:20', 'AnY', null, null, 1, null, true ],
+ [ '2017-02-07T15:20', 'aNy', null, null, 1, null, true ],
+ ]},
];
for (var test of testData) {
@@ -958,6 +1006,78 @@ function checkStepUp()
[ '2016-W01', 'AnY', null, null, 1, null, true ],
[ '2016-W01', 'aNy', null, null, 1, null, true ],
]},
+ { type: 'datetime-local', data: [
+ // Regular case.
+ [ '2017-02-07T17:09', null, null, null, null, '2017-02-07T17:10', false ],
+ // Argument testing.
+ [ '2017-02-07T17:10', null, null, null, 1, '2017-02-07T17:11', false ],
+ [ '2017-02-07T17:10', null, null, null, 5, '2017-02-07T17:15', false ],
+ [ '2017-02-07T17:10', null, null, null, -1, '2017-02-07T17:09', false ],
+ [ '2017-02-07T17:10', null, null, null, 0, '2017-02-07T17:10', false ],
+ // hour/minutes/seconds wrapping.
+ [ '2000-01-01T04:59', null, null, null, null, '2000-01-01T05:00', false ],
+ [ '2000-01-01T04:59:59', 1, null, null, null, '2000-01-01T05:00', false ],
+ [ '2000-01-01T04:59:59.900', 0.1, null, null, null, '2000-01-01T05:00', false ],
+ [ '2000-01-01T04:59:59.990', 0.01, null, null, null, '2000-01-01T05:00', false ],
+ [ '2000-01-01T04:59:59.999', 0.001, null, null, null, '2000-01-01T05:00', false ],
+ // month/year wrapping.
+ [ '2012-07-31T12:00', null, null, null, 1440, '2012-08-01T12:00', false ],
+ [ '1968-12-29T12:00', null, null, null, 5760, '1969-01-02T12:00', false ],
+ [ '1970-01-01T00:00', null, null, null, -1440, '1969-12-31T00:00', false ],
+ [ '2012-03-01T00:00', null, null, null, -1440, '2012-02-29T00:00', false ],
+ // stepUp() on '23:59' gives '00:00'.
+ [ '2017-02-07T23:59', null, null, null, 1, '2017-02-08T00:00', false ],
+ [ '2017-02-07T23:59', null, null, null, 3, '2017-02-08T00:02', false ],
+ // Some random step values..
+ [ '2017-02-07T17:40', '0.5', null, null, null, '2017-02-07T17:40:00.500', false ],
+ [ '2017-02-07T17:40', '2', null, null, null, '2017-02-07T17:40:02', false ],
+ [ '2017-02-07T17:40', '0.25', null, null, 4, '2017-02-07T17:40:01', false ],
+ [ '2017-02-07T17:40', '1.1', '2017-02-07T17:00', null, 1, '2017-02-07T17:40:00.200', false ],
+ [ '2017-02-07T17:40', '1.1', '2017-02-07T17:00', null, 2, '2017-02-07T17:40:01.300', false ],
+ [ '2017-02-07T17:40', '1.1', '2017-02-07T17:00', null, 10, '2017-02-07T17:40:10.100', false ],
+ [ '2017-02-07T17:40', '129600', '2017-02-01T00:00', null, 2, '2017-02-10T00:00', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '2017-02-07T17:39', '0', null, null, null, '2017-02-07T17:40', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '2017-02-07T17:39', '-1', null, null, null, '2017-02-07T17:40', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '2017-02-07T17:39', 'foo', null, null, null, '2017-02-07T17:40', false ],
+ // Min values testing.
+ [ '2012-02-02T17:00', '60', 'foo', null, 2, '2012-02-02T17:02', false ],
+ [ '2012-02-02T17:10', '60', '2012-02-02T17:10', null, null, '2012-02-02T17:11', false ],
+ [ '2012-02-02T17:10', '60', '2012-02-02T17:30', null, 1, '2012-02-02T17:30', false ],
+ [ '2012-02-02T17:10', '180', '2012-02-02T17:05', null, null, '2012-02-02T17:11', false ],
+ [ '2012-02-02T17:10', '86400', '2012-02-02T17:05', null, null, '2012-02-03T17:05', false ],
+ [ '2012-02-02T17:10', '129600', '2012-02-01T00:00', null, null, '2012-02-04T00:00', false ],
+ // Max values testing.
+ [ '2012-02-02T17:15', '60', null, 'foo', null, '2012-02-02T17:16', false ],
+ [ '2012-02-02T17:15', null, null, '2012-02-02T17:20', null, '2012-02-02T17:16', false ],
+ [ '2012-02-02T17:15', null, null, '2012-02-02T17:15', null, '2012-02-02T17:15', false ],
+ [ '2012-02-02T17:15', null, null, '2012-02-02T17:13', 4, '2012-02-02T17:15', false ],
+ [ '2012-02-02T20:05', '86400', null, '2012-02-03T20:05', null, '2012-02-03T20:05', false ],
+ [ '2012-02-02T18:00', '129600', null, '2012-02-04T20:00', null, '2012-02-04T06:00', false ],
+ // Step mismatch.
+ [ '2017-02-07T17:19', '120', '2017-02-07T17:10', null, null, '2017-02-07T17:20', false ],
+ [ '2017-02-07T17:19', '120', '2017-02-07T17:10', null, 2, '2017-02-07T17:22', false ],
+ [ '2017-02-07T17:19', '120', '2017-02-07T17:18', '2017-02-07T17:25', null, '2017-02-07T17:20', false ],
+ [ '2017-02-07T17:19', '120', null, null, null, '2017-02-07T17:21', false ],
+ [ '2017-02-07T17:19', '180', null, null, null, '2017-02-07T17:22', false ],
+ [ '2017-02-03T17:19', '172800', '2017-02-02T17:19', '2017-02-10T17:19', null, '2017-02-04T17:19', false ],
+ // Clamping.
+ [ '2017-02-07T17:22', null, null, '2017-02-07T17:11', null, '2017-02-07T17:22', false ],
+ [ '2017-02-07T17:22', '120', '2017-02-07T17:20', '2017-02-07T17:22', null, '2017-02-07T17:22', false ],
+ [ '2017-02-07T17:22', '300', '2017-02-07T17:12', '2017-02-07T17:20', 10, '2017-02-07T17:22', false ],
+ [ '2017-02-07T17:22', '300', '2017-02-07T17:18', '2017-02-07T17:20', 2, '2017-02-07T17:22', false ],
+ [ '2017-02-06T17:22', '600', '2017-02-02T17:00', '2017-02-07T17:20', 15, '2017-02-06T19:50', false ],
+ [ '2017-02-06T17:22', '600', '2017-02-02T17:10', '2017-02-07T17:20', 2, '2017-02-06T17:40', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '1970-01-01T00:01', false ],
+ // With step = 'any'.
+ [ '2017-02-07T17:30', 'any', null, null, 1, null, true ],
+ [ '2017-02-07T17:30', 'ANY', null, null, 1, null, true ],
+ [ '2017-02-07T17:30', 'AnY', null, null, 1, null, true ],
+ [ '2017-02-07T17:30', 'aNy', null, null, 1, null, true ],
+ ]},
];
for (var test of testData) {
diff --git a/dom/html/test/forms/test_valueAsDate_pref.html b/dom/html/test/forms/test_valueAsDate_pref.html
index 8518c291b..0369980e4 100644
--- a/dom/html/test/forms/test_valueAsDate_pref.html
+++ b/dom/html/test/forms/test_valueAsDate_pref.html
@@ -12,8 +12,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=874640
/** Test for Bug 874640 **/
var states = [
- // dom.experimental_forms, dom.forms.datepicker, dom.forms.datetime, expectedValueAsDate
- [ 'true', 'true', 'true', 'true' ],
+ // dom.experimental_forms, dom.forms.datetime, dom.forms.datetime.others, expectedValueAsDate
+ [ 'true', 'true', ,'true', 'true' ],
[ 'true', 'false', 'false', 'true' ],
[ 'false', 'true', 'false', 'true' ],
[ 'false', 'false', 'true', 'true' ],
@@ -33,8 +33,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=874640
SpecialPowers.pushPrefEnv({"set":[
["dom.experimental_forms", state[0] === 'true'],
- ["dom.forms.datepicker", state[1] === 'true'],
- ["dom.forms.datetime", state[2] === 'true']]},
+ ["dom.forms.datetime", state[1] === 'true'],
+ ["dom.forms.datetime.others", state[2] === 'true']]},
function() {
iframe.src = 'data:text/html,<script>' +
'parent.is("valueAsDate" in document.createElement("input"), ' +
diff --git a/dom/html/test/forms/test_valueasdate_attribute.html b/dom/html/test/forms/test_valueasdate_attribute.html
index 8c19fefd9..65cab3b8e 100644
--- a/dom/html/test/forms/test_valueasdate_attribute.html
+++ b/dom/html/test/forms/test_valueasdate_attribute.html
@@ -47,8 +47,7 @@ var validTypes =
["color", false],
["month", true],
["week", true],
- // TODO: temporary set to false until bug 888331 is fixed.
- ["datetime-local", false],
+ ["datetime-local", true],
];
function checkAvailability()
@@ -622,6 +621,107 @@ function checkWeekSet()
}
}
+function checkDatetimeLocalGet()
+{
+ var validData =
+ [
+ // Simple cases.
+ [ "2016-12-27T10:30", Date.UTC(2016, 11, 27, 10, 30, 0) ],
+ [ "2016-12-27T10:30:40", Date.UTC(2016, 11, 27, 10, 30, 40) ],
+ [ "2016-12-27T10:30:40.567", Date.UTC(2016, 11, 27, 10, 30, 40, 567) ],
+ [ "1969-12-31T12:00:00", Date.UTC(1969, 11, 31, 12, 0, 0) ],
+ [ "1970-01-01T00:00", 0 ],
+ // Leap years.
+ [ "1804-02-29 12:34", Date.UTC(1804, 1, 29, 12, 34, 0) ],
+ [ "2016-02-29T12:34", Date.UTC(2016, 1, 29, 12, 34, 0) ],
+ [ "2016-12-31T12:34:56", Date.UTC(2016, 11, 31, 12, 34, 56) ],
+ [ "2016-01-01T12:34:56.789", Date.UTC(2016, 0, 1, 12, 34, 56, 789) ],
+ [ "2017-01-01 12:34:56.789", Date.UTC(2017, 0, 1, 12, 34, 56, 789) ],
+ // Maximum valid datetime-local (limited by the ecma date object range).
+ [ "275760-09-13T00:00", 8640000000000000 ],
+ // Minimum valid datetime-local (limited by the input element minimum valid value).
+ [ "0001-01-01T00:00", -62135596800000 ],
+ ];
+
+ var invalidData =
+ [
+ [ "invaliddateime-local" ],
+ [ "0000-01-01T00:00" ],
+ [ "2016-12-25T00:00Z" ],
+ [ "2015-02-29T12:34" ],
+ [ "1-1-1T12:00" ],
+ [ "" ],
+ // This datetime-local is valid for the input element, but is out of the
+ // date object range. In this case, on getting valueAsDate, a Date object
+ // will be created, but it will have a NaN internal value, and will return
+ // the string "Invalid Date".
+ [ "275760-09-13T12:00", true ],
+ ];
+
+ element.type = "datetime-local";
+ for (let data of validData) {
+ element.value = data[0];
+ is(element.valueAsDate.valueOf(), data[1],
+ "valueAsDate should return the " +
+ "valid date object representing this datetime-local");
+ }
+
+ for (let data of invalidData) {
+ element.value = data[0];
+ if (data[1]) {
+ is(String(element.valueAsDate), "Invalid Date",
+ "valueAsDate should return an invalid Date object " +
+ "when the element value is not a valid datetime-local");
+ } else {
+ is(element.valueAsDate, null,
+ "valueAsDate should return null " +
+ "when the element value is not a valid datetime-local");
+ }
+ }
+}
+
+function checkDatetimeLocalSet()
+{
+ var testData =
+ [
+ // Simple cases.
+ [ Date.UTC(2016, 11, 27, 10, 30, 0), "2016-12-27T10:30" ],
+ [ Date.UTC(2016, 11, 27, 10, 30, 30), "2016-12-27T10:30:30" ],
+ [ Date.UTC(1999, 11, 31, 23, 59, 59), "1999-12-31T23:59:59" ],
+ [ Date.UTC(1999, 11, 31, 23, 59, 59, 999), "1999-12-31T23:59:59.999" ],
+ [ Date.UTC(123456, 7, 8, 9, 10), "123456-08-08T09:10" ],
+ [ 0, "1970-01-01T00:00" ],
+ // Maximum valid datetime-local (limited by the ecma date object range).
+ [ 8640000000000000, "275760-09-13T00:00" ],
+ // Minimum valid datetime-local (limited by the input element minimum valid value).
+ [ -62135596800000, "0001-01-01T00:00" ],
+ // Leap years.
+ [ Date.UTC(1804, 1, 29, 12, 34, 0), "1804-02-29T12:34" ],
+ [ Date.UTC(2016, 1, 29, 12, 34, 0), "2016-02-29T12:34" ],
+ [ Date.UTC(2016, 11, 31, 12, 34, 56), "2016-12-31T12:34:56" ],
+ [ Date.UTC(2016, 0, 1, 12, 34, 56, 789), "2016-01-01T12:34:56.789" ],
+ [ Date.UTC(2017, 0, 1, 12, 34, 56, 789), "2017-01-01T12:34:56.789" ],
+ // "Values must be truncated to valid datetime-local"
+ [ 123.123456789123, "1970-01-01T00:00:00.123" ],
+ [ 1e-1, "1970-01-01T00:00" ],
+ [ -1.1, "1969-12-31T23:59:59.999" ],
+ [ -345600000, "1969-12-28T00:00" ],
+ // Negative years, this is out of range for the input element,
+ // the corresponding datetime-local string is the empty string
+ [ -62135596800001, "" ],
+ ];
+
+ element.type = "datetime-local";
+ for (let data of testData) {
+ element.valueAsDate = new Date(data[0]);
+ is(element.value, data[1], "valueAsDate should set the value to " +
+ data[1]);
+ element.valueAsDate = new testFrame.Date(data[0]);
+ is(element.value, data[1], "valueAsDate with other-global date should " +
+ "set the value to " + data[1]);
+ }
+}
+
checkAvailability();
checkGarbageValues();
checkWithBustedPrototype();
@@ -642,6 +742,10 @@ checkMonthSet();
checkWeekGet();
checkWeekSet();
+// Test <input type='datetime-local'>.
+checkDatetimeLocalGet();
+checkDatetimeLocalSet();
+
</script>
</pre>
</body>
diff --git a/dom/html/test/forms/test_valueasnumber_attribute.html b/dom/html/test/forms/test_valueasnumber_attribute.html
index d7471502b..2660fc7ed 100644
--- a/dom/html/test/forms/test_valueasnumber_attribute.html
+++ b/dom/html/test/forms/test_valueasnumber_attribute.html
@@ -46,8 +46,7 @@ function checkAvailability()
["color", false],
["month", true],
["week", true],
- // TODO: temporary set to false until bug 888331 is fixed.
- ["datetime-local", false],
+ ["datetime-local", true],
];
var element = document.createElement('input');
@@ -612,7 +611,6 @@ function checkWeekGet()
var element = document.createElement('input');
element.type = "week";
for (let data of validData) {
- dump("Test: " + data[0]);
element.value = data[0];
is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
"integer value representing this week");
@@ -698,7 +696,120 @@ function checkWeekSet()
try {
element.valueAsNumber = data[0];
- is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
+ is(element.value, data[1], "valueAsNumber should set the value to " +
+ data[1]);
+ } catch(e) {
+ caught = true;
+ }
+
+ if (data[2]) {
+ ok(caught, "valueAsNumber should have thrown");
+ is(element.value, data[1], "the value should not have changed");
+ } else {
+ ok(!caught, "valueAsNumber should not have thrown");
+ }
+ }
+}
+
+function checkDatetimeLocalGet() {
+ var validData =
+ [
+ // Simple cases.
+ [ "2016-12-20T09:58", Date.UTC(2016, 11, 20, 9, 58) ],
+ [ "2016-12-20T09:58:30", Date.UTC(2016, 11, 20, 9, 58, 30) ],
+ [ "2016-12-20T09:58:30.123", Date.UTC(2016, 11, 20, 9, 58, 30, 123) ],
+ [ "2017-01-01T10:00", Date.UTC(2017, 0, 1, 10, 0, 0) ],
+ [ "1969-12-31T12:00:00", Date.UTC(1969, 11, 31, 12, 0, 0) ],
+ [ "1970-01-01T00:00", 0 ],
+ // Leap years.
+ [ "1804-02-29 12:34", Date.UTC(1804, 1, 29, 12, 34, 0) ],
+ [ "2016-02-29T12:34", Date.UTC(2016, 1, 29, 12, 34, 0) ],
+ [ "2016-12-31T12:34:56", Date.UTC(2016, 11, 31, 12, 34, 56) ],
+ [ "2016-01-01T12:34:56.789", Date.UTC(2016, 0, 1, 12, 34, 56, 789) ],
+ [ "2017-01-01 12:34:56.789", Date.UTC(2017, 0, 1, 12, 34, 56, 789) ],
+ // Maximum valid datetime-local (limited by the ecma date object range).
+ [ "275760-09-13T00:00", 8640000000000000 ],
+ // Minimum valid datetime-local (limited by the input element minimum valid value).
+ [ "0001-01-01T00:00", -62135596800000 ],
+ ];
+
+ var invalidData =
+ [
+ "invaliddatetime-local",
+ "0000-01-01T00:00",
+ "2016-12-25T00:00Z",
+ "2015-02-29T12:34",
+ "1-1-1T12:00",
+ // Out of range.
+ "275760-09-13T12:00",
+ ];
+
+ var element = document.createElement('input');
+ element.type = "datetime-local";
+ for (let data of validData) {
+ element.value = data[0];
+ is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+ "integer value representing this datetime-local");
+ }
+
+ for (let data of invalidData) {
+ element.value = data;
+ ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN " +
+ "when the element value is not a valid datetime-local");
+ }
+}
+
+function checkDatetimeLocalSet()
+{
+ var testData =
+ [
+ // Simple cases.
+ [ Date.UTC(2016, 11, 20, 9, 58, 0), "2016-12-20T09:58", ],
+ [ Date.UTC(2016, 11, 20, 9, 58, 30), "2016-12-20T09:58:30" ],
+ [ Date.UTC(2016, 11, 20, 9, 58, 30, 123), "2016-12-20T09:58:30.123" ],
+ [ Date.UTC(2017, 0, 1, 10, 0, 0), "2017-01-01T10:00" ],
+ [ Date.UTC(1969, 11, 31, 12, 0, 0), "1969-12-31T12:00" ],
+ [ 0, "1970-01-01T00:00" ],
+ // Maximum valid week (limited by the ecma date object range).
+ [ 8640000000000000, "275760-09-13T00:00" ],
+ // Minimum valid datetime-local (limited by the input element minimum valid value).
+ [ -62135596800000, "0001-01-01T00:00" ],
+ // Leap years.
+ [ Date.UTC(1804, 1, 29, 12, 34, 0), "1804-02-29T12:34" ],
+ [ Date.UTC(2016, 1, 29, 12, 34, 0), "2016-02-29T12:34" ],
+ [ Date.UTC(2016, 11, 31, 12, 34, 56), "2016-12-31T12:34:56" ],
+ [ Date.UTC(2016, 0, 1, 12, 34, 56, 789), "2016-01-01T12:34:56.789" ],
+ [ Date.UTC(2017, 0, 1, 12, 34, 56, 789), "2017-01-01T12:34:56.789" ],
+ // "Values must be truncated to valid datetime-local"
+ [ 0.3, "1970-01-01T00:00" ],
+ [ 1e-1, "1970-01-01T00:00" ],
+ [ -1 , "1969-12-31T23:59:59.999" ],
+ [ -345600000, "1969-12-28T00:00" ],
+ // Invalid numbers.
+ // Those are implicitly converted to numbers
+ [ "", "1970-01-01T00:00" ],
+ [ true, "1970-01-01T00:00:00.001" ],
+ [ false, "1970-01-01T00:00" ],
+ [ null, "1970-01-01T00:00" ],
+ // Those are converted to NaN, the corresponding week string is the empty string
+ [ "invaliddatetime-local", "" ],
+ [ NaN, "" ],
+ [ undefined, "" ],
+ // Infinity will keep the current value and throw (so we need to set a current value).
+ [ Date.UTC(2016, 11, 27, 15, 10, 0), "2016-12-27T15:10" ],
+ [ Infinity, "2016-12-27T15:10", true ],
+ [ -Infinity, "2016-12-27T15:10", true ],
+ ];
+
+ var element = document.createElement('input');
+ element.type = "datetime-local";
+ for (let data of testData) {
+ var caught = false;
+
+ try {
+ element.valueAsNumber = data[0];
+ is(element.value, data[1], "valueAsNumber should set the value to " +
+ data[1]);
} catch(e) {
caught = true;
}
@@ -738,6 +849,10 @@ checkMonthSet();
checkWeekGet();
checkWeekSet();
+// <input type='datetime-local'> test
+checkDatetimeLocalGet();
+checkDatetimeLocalSet();
+
</script>
</pre>
</body>
diff --git a/dom/html/test/test_bug558788-1.html b/dom/html/test/test_bug558788-1.html
index 94b7a5f00..4db61ed73 100644
--- a/dom/html/test/test_bug558788-1.html
+++ b/dom/html/test/test_bug558788-1.html
@@ -154,13 +154,14 @@ function checkInputURL()
sendString("ttp://mozilla.org");
checkValidApplies(element);
- for (var i=0; i<13; ++i) {
+ for (var i=0; i<10; ++i) {
synthesizeKey("VK_BACK_SPACE", {});
checkValidApplies(element);
}
synthesizeKey("VK_BACK_SPACE", {});
- for (var i=0; i<4; ++i) {
+ // "http://" is now invalid
+ for (var i=0; i<7; ++i) {
checkInvalidApplies(element);
synthesizeKey("VK_BACK_SPACE", {});
}
diff --git a/dom/ipc/DatePickerParent.cpp b/dom/ipc/DatePickerParent.cpp
deleted file mode 100644
index 509944ddd..000000000
--- a/dom/ipc/DatePickerParent.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "DatePickerParent.h"
-#include "nsComponentManagerUtils.h"
-#include "nsIDocument.h"
-#include "nsIDOMWindow.h"
-#include "mozilla/Unused.h"
-#include "mozilla/dom/Element.h"
-#include "mozilla/dom/TabParent.h"
-
-using mozilla::Unused;
-using namespace mozilla::dom;
-
-NS_IMPL_ISUPPORTS(DatePickerParent::DatePickerShownCallback,
- nsIDatePickerShownCallback);
-
-NS_IMETHODIMP
-DatePickerParent::DatePickerShownCallback::Cancel()
-{
- if (mDatePickerParent) {
- Unused << mDatePickerParent->SendCancel();
- }
- return NS_OK;
-}
-
-NS_IMETHODIMP
-DatePickerParent::DatePickerShownCallback::Done(const nsAString& aDate)
-{
- if (mDatePickerParent) {
- Unused << mDatePickerParent->Send__delete__(mDatePickerParent,
- nsString(aDate));
- }
- return NS_OK;
-}
-
-void
-DatePickerParent::DatePickerShownCallback::Destroy()
-{
- mDatePickerParent = nullptr;
-}
-
-bool
-DatePickerParent::CreateDatePicker()
-{
- mPicker = do_CreateInstance("@mozilla.org/datepicker;1");
- if (!mPicker) {
- return false;
- }
-
- Element* ownerElement = TabParent::GetFrom(Manager())->GetOwnerElement();
- if (!ownerElement) {
- return false;
- }
-
- nsCOMPtr<mozIDOMWindowProxy> window = do_QueryInterface(ownerElement->OwnerDoc()->GetWindow());
- if (!window) {
- return false;
- }
-
- return NS_SUCCEEDED(mPicker->Init(window, mTitle, mInitialDate));
-}
-
-bool
-DatePickerParent::RecvOpen()
-{
- if (!CreateDatePicker()) {
- Unused << Send__delete__(this, mInitialDate);
- return true;
- }
-
- mCallback = new DatePickerShownCallback(this);
-
- mPicker->Open(mCallback);
- return true;
-};
-
-void
-DatePickerParent::ActorDestroy(ActorDestroyReason aWhy)
-{
- if (mCallback) {
- mCallback->Destroy();
- }
-}
diff --git a/dom/ipc/DatePickerParent.h b/dom/ipc/DatePickerParent.h
deleted file mode 100644
index 73b66f96c..000000000
--- a/dom/ipc/DatePickerParent.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_DatePickerParent_h
-#define mozilla_dom_DatePickerParent_h
-
-#include "mozilla/dom/PDatePickerParent.h"
-#include "nsIDatePicker.h"
-
-namespace mozilla {
-namespace dom {
-
-class DatePickerParent : public PDatePickerParent
-{
- public:
- DatePickerParent(const nsString& aTitle,
- const nsString& aInitialDate)
- : mTitle(aTitle)
- , mInitialDate(aInitialDate)
- {}
-
- virtual bool RecvOpen() override;
- virtual void ActorDestroy(ActorDestroyReason aWhy) override;
-
- class DatePickerShownCallback final
- : public nsIDatePickerShownCallback
- {
- public:
- explicit DatePickerShownCallback(DatePickerParent* aDatePickerParnet)
- : mDatePickerParent(aDatePickerParnet)
- {}
-
- NS_DECL_ISUPPORTS
- NS_DECL_NSIDATEPICKERSHOWNCALLBACK
-
- void Destroy();
-
- private:
- ~DatePickerShownCallback() {}
- DatePickerParent* mDatePickerParent;
- };
-
- private:
- virtual ~DatePickerParent() {}
-
- bool CreateDatePicker();
-
- RefPtr<DatePickerShownCallback> mCallback;
- nsCOMPtr<nsIDatePicker> mPicker;
-
- nsString mTitle;
- nsString mInitialDate;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_DatePickerParent_h
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl
index 9dfccbc5c..f09e484ee 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -9,7 +9,6 @@ include protocol PBlob;
include protocol PColorPicker;
include protocol PContent;
include protocol PContentBridge;
-include protocol PDatePicker;
include protocol PDocAccessible;
include protocol PFilePicker;
include protocol PIndexedDBPermissionRequest;
@@ -116,7 +115,6 @@ nested(upto inside_cpow) sync protocol PBrowser
manager PContent or PContentBridge;
manages PColorPicker;
- manages PDatePicker;
manages PDocAccessible;
manages PFilePicker;
manages PIndexedDBPermissionRequest;
@@ -441,12 +439,6 @@ parent:
*/
async PColorPicker(nsString title, nsString initialColor);
- /**
- * Create an asynchronous date picker on the parent side,
- * but don't open it yet.
- */
- async PDatePicker(nsString title, nsString initialDate);
-
async PFilePicker(nsString aTitle, int16_t aMode);
/**
diff --git a/dom/ipc/PDatePicker.ipdl b/dom/ipc/PDatePicker.ipdl
deleted file mode 100644
index 90a2654bb..000000000
--- a/dom/ipc/PDatePicker.ipdl
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */
-/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-include protocol PBrowser;
-
-namespace mozilla {
-namespace dom {
-
-protocol PDatePicker
-{
- manager PBrowser;
-
-parent:
- async Open();
-
-child:
- async Cancel();
-
- async __delete__(nsString color);
-};
-
-} // namespace dom
-} // namespace mozilla
diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp
index c8a0c6e3f..705799c54 100644
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -96,7 +96,6 @@
#include "LayersLogging.h"
#include "nsDOMClassInfoID.h"
#include "nsColorPickerProxy.h"
-#include "nsDatePickerProxy.h"
#include "nsContentPermissionHelper.h"
#include "nsPresShell.h"
#include "nsIAppsService.h"
@@ -2013,21 +2012,6 @@ TabChild::DeallocPColorPickerChild(PColorPickerChild* aColorPicker)
return true;
}
-PDatePickerChild*
-TabChild::AllocPDatePickerChild(const nsString&, const nsString&)
-{
- NS_RUNTIMEABORT("unused");
- return nullptr;
-}
-
-bool
-TabChild::DeallocPDatePickerChild(PDatePickerChild* aDatePicker)
-{
- nsDatePickerProxy* picker = static_cast<nsDatePickerProxy*>(aDatePicker);
- NS_RELEASE(picker);
- return true;
-}
-
PFilePickerChild*
TabChild::AllocPFilePickerChild(const nsString&, const int16_t&)
{
diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h
index 2232ffeaf..d9988a596 100644
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -451,10 +451,6 @@ public:
virtual bool DeallocPColorPickerChild(PColorPickerChild* aActor) override;
- virtual PDatePickerChild*
- AllocPDatePickerChild(const nsString& title, const nsString& initialDate) override;
- virtual bool DeallocPDatePickerChild(PDatePickerChild* actor) override;
-
virtual PFilePickerChild*
AllocPFilePickerChild(const nsString& aTitle, const int16_t& aMode) override;
diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp
index 8e98de3ce..0f190708f 100644
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -80,7 +80,6 @@
#include "PermissionMessageUtils.h"
#include "StructuredCloneData.h"
#include "ColorPickerParent.h"
-#include "DatePickerParent.h"
#include "FilePickerParent.h"
#include "TabChild.h"
#include "LoadContext.h"
@@ -2424,20 +2423,6 @@ TabParent::DeallocPColorPickerParent(PColorPickerParent* actor)
return true;
}
-PDatePickerParent*
-TabParent::AllocPDatePickerParent(const nsString& aTitle,
- const nsString& aInitialDate)
-{
- return new DatePickerParent(aTitle, aInitialDate);
-}
-
-bool
-TabParent::DeallocPDatePickerParent(PDatePickerParent* actor)
-{
- delete actor;
- return true;
-}
-
PRenderFrameParent*
TabParent::AllocPRenderFrameParent()
{
diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h
index 09bb999f3..3624ce320 100644
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -347,10 +347,6 @@ public:
virtual bool
DeallocPColorPickerParent(PColorPickerParent* aColorPicker) override;
- virtual PDatePickerParent*
- AllocPDatePickerParent(const nsString& aTitle, const nsString& aInitialDate) override;
- virtual bool DeallocPDatePickerParent(PDatePickerParent* aDatePicker) override;
-
virtual PDocAccessibleParent*
AllocPDocAccessibleParent(PDocAccessibleParent*, const uint64_t&,
const uint32_t&, const IAccessibleHolder&) override;
diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build
index ff3880bc2..71d193d44 100644
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -55,7 +55,6 @@ UNIFIED_SOURCES += [
'ContentProcess.cpp',
'ContentProcessManager.cpp',
'CrashReporterParent.cpp',
- 'DatePickerParent.cpp',
'FilePickerParent.cpp',
'nsIContentChild.cpp',
'nsIContentParent.cpp',
@@ -95,7 +94,6 @@ IPDL_SOURCES += [
'PContentPermissionRequest.ipdl',
'PCrashReporter.ipdl',
'PCycleCollectWithLogs.ipdl',
- 'PDatePicker.ipdl',
'PFilePicker.ipdl',
'PMemoryReportRequest.ipdl',
'PPluginWidget.ipdl',
diff --git a/dom/locales/en-US/chrome/layout/HtmlForm.properties b/dom/locales/en-US/chrome/layout/HtmlForm.properties
index 621a0b2b3..82274b2bc 100644
--- a/dom/locales/en-US/chrome/layout/HtmlForm.properties
+++ b/dom/locales/en-US/chrome/layout/HtmlForm.properties
@@ -34,7 +34,6 @@ NoDirSelected=No directory selected.
# %S will be a number greater or equal to 2.
XFilesSelected=%S files selected.
ColorPicker=Choose a color
-DatePicker=Choose a date
# LOCALIZATION NOTE (AndNMoreFiles): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# This string is shown at the end of the tooltip text for <input type='file'
diff --git a/dom/url/tests/test_url.html b/dom/url/tests/test_url.html
index d07a752bb..73e75667d 100644
--- a/dom/url/tests/test_url.html
+++ b/dom/url/tests/test_url.html
@@ -399,6 +399,18 @@
</script>
<script>
+ /** Test for Bug 1275746 **/
+ SimpleTest.doesThrow(() => { var url = new URL("http:"); }, "http: is not a valid URL");
+ SimpleTest.doesThrow(() => { var url = new URL("http:///"); }, "http: is not a valid URL");
+
+ var url = new URL("file:");
+ is(url.href, "file:///", "Parsing file: should work.");
+
+ url = new URL("file:///");
+ is(url.href, "file:///", "Parsing file:/// should work.");
+ </script>
+
+ <script>
var url = new URL("scheme:path/to/file?query#hash");
is(url.href, "scheme:path/to/file?query#hash");
is(url.pathname, "path/to/file");
diff --git a/dom/webidl/CanvasRenderingContext2D.webidl b/dom/webidl/CanvasRenderingContext2D.webidl
index 38a30f9e3..1c5564215 100644
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -27,6 +27,9 @@ dictionary HitRegionOptions {
};
typedef (HTMLImageElement or
+ SVGImageElement) HTMLOrSVGImageElement;
+
+typedef (HTMLOrSVGImageElement or
HTMLCanvasElement or
HTMLVideoElement or
ImageBitmap) CanvasImageSource;
diff --git a/dom/webidl/HTMLInputElement.webidl b/dom/webidl/HTMLInputElement.webidl
index 050d19510..cf3e9a4c7 100644
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -238,6 +238,9 @@ partial interface HTMLInputElement {
dictionary DateTimeValue {
long hour;
long minute;
+ long year;
+ long month;
+ long day;
};
partial interface HTMLInputElement {
@@ -250,6 +253,14 @@ partial interface HTMLInputElement {
[Pref="dom.forms.datetime", ChromeOnly]
void setDateTimePickerState(boolean open);
+ [Pref="dom.forms.datetime", ChromeOnly,
+ BinaryName="getMinimumAsDouble"]
+ double getMinimum();
+
+ [Pref="dom.forms.datetime", ChromeOnly,
+ BinaryName="getMaximumAsDouble"]
+ double getMaximum();
+
[Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
void openDateTimePicker(optional DateTimeValue initialValue);
@@ -258,4 +269,12 @@ partial interface HTMLInputElement {
[Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
void closeDateTimePicker();
+
+ [Pref="dom.forms.datetime", Func="IsChromeOrXBL",
+ BinaryName="getStepAsDouble"]
+ double getStep();
+
+ [Pref="dom.forms.datetime", Func="IsChromeOrXBL",
+ BinaryName="getStepBaseAsDouble"]
+ double getStepBase();
};
diff --git a/editor/libeditor/HTMLEditorDataTransfer.cpp b/editor/libeditor/HTMLEditorDataTransfer.cpp
index b9cd8adb9..ed350c0dd 100644
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -2382,27 +2382,26 @@ HTMLEditor::ReplaceOrphanedStructure(
}
// If we found substructure, paste it instead of its descendants.
- // Only replace with the substructure if all the nodes in the list are
- // descendants.
- bool shouldReplaceNodes = true;
- for (uint32_t i = 0; i < aNodeArray.Length(); i++) {
+ // Postprocess list to remove any descendants of this node so that we don't
+ // insert them twice.
+ uint32_t removedCount = 0;
+ uint32_t originalLength = aNodeArray.Length();
+ for (uint32_t i = 0; i < originalLength; i++) {
uint32_t idx = aStartOrEnd == StartOrEnd::start ?
- i : (aNodeArray.Length() - i - 1);
+ (i - removedCount) : (originalLength - i - 1);
OwningNonNull<nsINode> endpoint = aNodeArray[idx];
- if (!EditorUtils::IsDescendantOf(endpoint, replaceNode)) {
- shouldReplaceNodes = false;
- break;
+ if (endpoint == replaceNode ||
+ EditorUtils::IsDescendantOf(endpoint, replaceNode)) {
+ aNodeArray.RemoveElementAt(idx);
+ removedCount++;
}
}
- if (shouldReplaceNodes) {
- // Now replace the removed nodes with the structural parent
- aNodeArray.Clear();
- if (aStartOrEnd == StartOrEnd::end) {
- aNodeArray.AppendElement(*replaceNode);
- } else {
- aNodeArray.InsertElementAt(0, *replaceNode);
- }
+ // Now replace the removed nodes with the structural parent
+ if (aStartOrEnd == StartOrEnd::end) {
+ aNodeArray.AppendElement(*replaceNode);
+ } else {
+ aNodeArray.InsertElementAt(0, *replaceNode);
}
}
diff --git a/editor/libeditor/tests/mochitest.ini b/editor/libeditor/tests/mochitest.ini
index 9aafa0ac2..4df3f606b 100644
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -217,6 +217,9 @@ skip-if = toolkit == 'android'
[test_bug1258085.html]
[test_bug1268736.html]
[test_bug1270235.html]
+[test_bug1306532.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
[test_bug1310912.html]
skip-if = toolkit == 'android' # bug 1315898
[test_bug1314790.html]
diff --git a/editor/libeditor/tests/test_bug1306532.html b/editor/libeditor/tests/test_bug1306532.html
new file mode 100644
index 000000000..1d7b3e7af
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1306532.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html><head>
+<title>Test for bug 1306532</title>
+<style src="/tests/SimpleTest/test.css" type="text/css"></style>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script class="testbody" type="application/javascript">
+
+function runTest() {
+ // Copy content from table.
+ var selection = getSelection();
+ var startRange = document.createRange();
+ startRange.setStart(headingone, 0);
+ startRange.setEnd(celltwo, 0);
+ selection.removeAllRanges();
+ selection.addRange(startRange);
+ SpecialPowers.wrap(document).execCommand("copy", false, null);
+
+ // Paste content into "pasteframe"
+ var pasteContainer = pasteframe.contentDocument.body;
+ var pasteRange = pasteframe.contentDocument.createRange();
+ pasteRange.selectNodeContents(pasteContainer);
+ pasteRange.collapse(false);
+ selection.removeAllRanges();
+ selection.addRange(pasteRange);
+ SpecialPowers.wrap(pasteframe.contentDocument).execCommand("paste", false, null);
+
+ is(pasteContainer.querySelector("#headingone").textContent, "Month", "First heading should be 'Month'.");
+ is(pasteContainer.querySelector("#headingtwo").textContent, "Savings", "Second heading should be 'Savings'.");
+ is(pasteContainer.querySelector("#cellone").textContent, "January", "First cell should be 'January'.");
+ is(pasteContainer.querySelector("#celltwo").textContent, "$100", "Second cell should be '$100'.");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1306532">Mozilla Bug 1306532</a>
+<p id="display"></p>
+
+<pre id="test">
+</pre>
+
+<div id="container">
+<table border="1">
+ <tr>
+ <th id="headingone">Month</th>
+ <th id="headingtwo">Savings</th>
+ </tr>
+ <tr>
+ <td id="cellone">January</td>
+ <td id="celltwo">$100</td>
+ </tr>
+</table>
+</div>
+
+<iframe onload="runTest();" id="pasteframe" src="data:text/html,<html><body contenteditable='true'>"></iframe>
+
+</body>
+</html>
diff --git a/extensions/cookie/test/unit/test_bug526789.js b/extensions/cookie/test/unit/test_bug526789.js
index 0eac1d492..624044577 100644
--- a/extensions/cookie/test/unit/test_bug526789.js
+++ b/extensions/cookie/test/unit/test_bug526789.js
@@ -79,23 +79,11 @@ function run_test() {
cm.removeAll();
- // test that setting an empty or '.' http:// host results in a no-op
var uri = NetUtil.newURI("http://baz.com/");
- var emptyuri = NetUtil.newURI("http:///");
- var doturi = NetUtil.newURI("http://./");
do_check_eq(uri.asciiHost, "baz.com");
- do_check_eq(emptyuri.asciiHost, "");
- do_check_eq(doturi.asciiHost, ".");
- cs.setCookieString(emptyuri, null, "foo2=bar", null);
- do_check_eq(getCookieCount(), 0);
- cs.setCookieString(doturi, null, "foo3=bar", null);
- do_check_eq(getCookieCount(), 0);
cs.setCookieString(uri, null, "foo=bar", null);
- do_check_eq(getCookieCount(), 1);
do_check_eq(cs.getCookieString(uri, null), "foo=bar");
- do_check_eq(cs.getCookieString(emptyuri, null), null);
- do_check_eq(cs.getCookieString(doturi, null), null);
do_check_eq(cm.countCookiesFromHost(""), 0);
do_check_throws(function() {
diff --git a/js/public/Date.h b/js/public/Date.h
index cba0ea875..cab36a3de 100644
--- a/js/public/Date.h
+++ b/js/public/Date.h
@@ -134,6 +134,14 @@ NewDateObject(JSContext* cx, ClippedTime time);
JS_PUBLIC_API(double)
MakeDate(double year, unsigned month, unsigned day);
+// Year is a year, month is 0-11, day is 1-based, and time is in milliseconds.
+// The return value is a number of milliseconds since the epoch.
+//
+// Consistent with the MakeDate algorithm defined in ECMAScript, this value is
+// *not* clipped! Use JS::TimeClip if you need a clipped date.
+JS_PUBLIC_API(double)
+MakeDate(double year, unsigned month, unsigned day, double time);
+
// Takes an integer number of milliseconds since the epoch and returns the
// year. Can return NaN, and will do so if NaN is passed in.
JS_PUBLIC_API(double)
diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp
index 3a20c487b..cd808cb10 100644
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -102,6 +102,18 @@ Char16ToUChar(char16_t* chars)
MOZ_CRASH("Char16ToUChar: Intl API disabled");
}
+inline char16_t*
+UCharToChar16(UChar* chars)
+{
+ MOZ_CRASH("UCharToChar16: Intl API disabled");
+}
+
+inline const char16_t*
+UCharToChar16(const UChar* chars)
+{
+ MOZ_CRASH("UCharToChar16: Intl API disabled");
+}
+
static int32_t
u_strlen(const UChar* s)
{
@@ -356,6 +368,27 @@ enum UCalendarDateFields {
UCAL_DAY_OF_MONTH = UCAL_DATE
};
+enum UCalendarMonths {
+ UCAL_JANUARY,
+ UCAL_FEBRUARY,
+ UCAL_MARCH,
+ UCAL_APRIL,
+ UCAL_MAY,
+ UCAL_JUNE,
+ UCAL_JULY,
+ UCAL_AUGUST,
+ UCAL_SEPTEMBER,
+ UCAL_OCTOBER,
+ UCAL_NOVEMBER,
+ UCAL_DECEMBER,
+ UCAL_UNDECIMBER
+};
+
+enum UCalendarAMPMs {
+ UCAL_AM,
+ UCAL_PM
+};
+
static UCalendar*
ucal_open(const UChar* zoneID, int32_t len, const char* locale,
UCalendarType type, UErrorCode* status)
@@ -420,6 +453,13 @@ ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* statu
MOZ_CRASH("ucal_getDefaultTimeZone: Intl API disabled");
}
+enum UDateTimePatternField {
+ UDATPG_YEAR_FIELD,
+ UDATPG_MONTH_FIELD,
+ UDATPG_WEEK_OF_YEAR_FIELD,
+ UDATPG_DAY_FIELD,
+};
+
typedef void* UDateTimePatternGenerator;
static UDateTimePatternGenerator*
@@ -436,6 +476,14 @@ udatpg_getBestPattern(UDateTimePatternGenerator* dtpg, const UChar* skeleton,
MOZ_CRASH("udatpg_getBestPattern: Intl API disabled");
}
+static const UChar *
+udatpg_getAppendItemName(const UDateTimePatternGenerator *dtpg,
+ UDateTimePatternField field,
+ int32_t *pLength)
+{
+ MOZ_CRASH("udatpg_getAppendItemName: Intl API disabled");
+}
+
static void
udatpg_close(UDateTimePatternGenerator* dtpg)
{
@@ -488,10 +536,46 @@ enum UDateFormatField {
};
enum UDateFormatStyle {
+ UDAT_FULL,
+ UDAT_LONG,
+ UDAT_MEDIUM,
+ UDAT_SHORT,
+ UDAT_DEFAULT = UDAT_MEDIUM,
UDAT_PATTERN = -2,
UDAT_IGNORE = UDAT_PATTERN
};
+enum UDateFormatSymbolType {
+ UDAT_ERAS,
+ UDAT_MONTHS,
+ UDAT_SHORT_MONTHS,
+ UDAT_WEEKDAYS,
+ UDAT_SHORT_WEEKDAYS,
+ UDAT_AM_PMS,
+ UDAT_LOCALIZED_CHARS,
+ UDAT_ERA_NAMES,
+ UDAT_NARROW_MONTHS,
+ UDAT_NARROW_WEEKDAYS,
+ UDAT_STANDALONE_MONTHS,
+ UDAT_STANDALONE_SHORT_MONTHS,
+ UDAT_STANDALONE_NARROW_MONTHS,
+ UDAT_STANDALONE_WEEKDAYS,
+ UDAT_STANDALONE_SHORT_WEEKDAYS,
+ UDAT_STANDALONE_NARROW_WEEKDAYS,
+ UDAT_QUARTERS,
+ UDAT_SHORT_QUARTERS,
+ UDAT_STANDALONE_QUARTERS,
+ UDAT_STANDALONE_SHORT_QUARTERS,
+ UDAT_SHORTER_WEEKDAYS,
+ UDAT_STANDALONE_SHORTER_WEEKDAYS,
+ UDAT_CYCLIC_YEARS_WIDE,
+ UDAT_CYCLIC_YEARS_ABBREVIATED,
+ UDAT_CYCLIC_YEARS_NARROW,
+ UDAT_ZODIAC_NAMES_WIDE,
+ UDAT_ZODIAC_NAMES_ABBREVIATED,
+ UDAT_ZODIAC_NAMES_NARROW
+};
+
static int32_t
udat_countAvailable()
{
@@ -563,6 +647,13 @@ udat_close(UDateFormat* format)
MOZ_CRASH("udat_close: Intl API disabled");
}
+static int32_t
+udat_getSymbols(const UDateFormat *fmt, UDateFormatSymbolType type, int32_t symbolIndex,
+ UChar *result, int32_t resultLength, UErrorCode *status)
+{
+ MOZ_CRASH("udat_getSymbols: Intl API disabled");
+}
+
#endif
@@ -2921,6 +3012,296 @@ js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp)
return true;
}
+template<size_t N>
+inline bool
+MatchPart(const char** pattern, const char (&part)[N])
+{
+ if (strncmp(*pattern, part, N - 1))
+ return false;
+
+ *pattern += N - 1;
+ return true;
+}
+
+bool
+js::intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+ // 1. Assert: locale is a string.
+ MOZ_ASSERT(args[0].isString());
+ // 2. Assert: style is a string.
+ MOZ_ASSERT(args[1].isString());
+ // 3. Assert: keys is an Array.
+ MOZ_ASSERT(args[2].isObject());
+
+ JSAutoByteString locale(cx, args[0].toString());
+ if (!locale)
+ return false;
+
+ JSAutoByteString style(cx, args[1].toString());
+ if (!style)
+ return false;
+
+ RootedArrayObject keys(cx, &args[2].toObject().as<ArrayObject>());
+ if (!keys)
+ return false;
+
+ // 4. Let result be ArrayCreate(0).
+ RootedArrayObject result(cx, NewDenseUnallocatedArray(cx, keys->length()));
+ if (!result)
+ return false;
+
+ UErrorCode status = U_ZERO_ERROR;
+
+ UDateFormat* fmt =
+ udat_open(UDAT_DEFAULT, UDAT_DEFAULT, icuLocale(locale.ptr()),
+ nullptr, 0, nullptr, 0, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
+
+ // UDateTimePatternGenerator will be needed for translations of date and
+ // time fields like "month", "week", "day" etc.
+ UDateTimePatternGenerator* dtpg = udatpg_open(icuLocale(locale.ptr()), &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+ ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
+
+ RootedValue keyValue(cx);
+ RootedString keyValStr(cx);
+ RootedValue wordVal(cx);
+ Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+ if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+ return false;
+
+ // 5. For each element of keys,
+ for (uint32_t i = 0; i < keys->length(); i++) {
+ /**
+ * We iterate over keys array looking for paths that we have code
+ * branches for.
+ *
+ * For any unknown path branch, the wordVal will keep NullValue and
+ * we'll throw at the end.
+ */
+
+ if (!GetElement(cx, keys, keys, i, &keyValue))
+ return false;
+
+ JSAutoByteString pattern;
+ keyValStr = keyValue.toString();
+ if (!pattern.encodeUtf8(cx, keyValStr))
+ return false;
+
+ wordVal.setNull();
+
+ // 5.a. Perform an implementation dependent algorithm to map a key to a
+ // corresponding display name.
+ const char* pat = pattern.ptr();
+
+ if (!MatchPart(&pat, "dates")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ if (MatchPart(&pat, "fields")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ UDateTimePatternField fieldType;
+
+ if (MatchPart(&pat, "year")) {
+ fieldType = UDATPG_YEAR_FIELD;
+ } else if (MatchPart(&pat, "month")) {
+ fieldType = UDATPG_MONTH_FIELD;
+ } else if (MatchPart(&pat, "week")) {
+ fieldType = UDATPG_WEEK_OF_YEAR_FIELD;
+ } else if (MatchPart(&pat, "day")) {
+ fieldType = UDATPG_DAY_FIELD;
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ // This part must be the final part with no trailing data.
+ if (*pat != '\0') {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ int32_t resultSize;
+
+ const UChar* value = udatpg_getAppendItemName(dtpg, fieldType, &resultSize);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ JSString* word = NewStringCopyN<CanGC>(cx, UCharToChar16(value), resultSize);
+ if (!word)
+ return false;
+
+ wordVal.setString(word);
+ } else if (MatchPart(&pat, "gregorian")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ UDateFormatSymbolType symbolType;
+ int32_t index;
+
+ if (MatchPart(&pat, "months")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ if (equal(style, "narrow")) {
+ symbolType = UDAT_STANDALONE_NARROW_MONTHS;
+ } else if (equal(style, "short")) {
+ symbolType = UDAT_STANDALONE_SHORT_MONTHS;
+ } else {
+ MOZ_ASSERT(equal(style, "long"));
+ symbolType = UDAT_STANDALONE_MONTHS;
+ }
+
+ if (MatchPart(&pat, "january")) {
+ index = UCAL_JANUARY;
+ } else if (MatchPart(&pat, "february")) {
+ index = UCAL_FEBRUARY;
+ } else if (MatchPart(&pat, "march")) {
+ index = UCAL_MARCH;
+ } else if (MatchPart(&pat, "april")) {
+ index = UCAL_APRIL;
+ } else if (MatchPart(&pat, "may")) {
+ index = UCAL_MAY;
+ } else if (MatchPart(&pat, "june")) {
+ index = UCAL_JUNE;
+ } else if (MatchPart(&pat, "july")) {
+ index = UCAL_JULY;
+ } else if (MatchPart(&pat, "august")) {
+ index = UCAL_AUGUST;
+ } else if (MatchPart(&pat, "september")) {
+ index = UCAL_SEPTEMBER;
+ } else if (MatchPart(&pat, "october")) {
+ index = UCAL_OCTOBER;
+ } else if (MatchPart(&pat, "november")) {
+ index = UCAL_NOVEMBER;
+ } else if (MatchPart(&pat, "december")) {
+ index = UCAL_DECEMBER;
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+ } else if (MatchPart(&pat, "weekdays")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ if (equal(style, "narrow")) {
+ symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS;
+ } else if (equal(style, "short")) {
+ symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS;
+ } else {
+ MOZ_ASSERT(equal(style, "long"));
+ symbolType = UDAT_STANDALONE_WEEKDAYS;
+ }
+
+ if (MatchPart(&pat, "monday")) {
+ index = UCAL_MONDAY;
+ } else if (MatchPart(&pat, "tuesday")) {
+ index = UCAL_TUESDAY;
+ } else if (MatchPart(&pat, "wednesday")) {
+ index = UCAL_WEDNESDAY;
+ } else if (MatchPart(&pat, "thursday")) {
+ index = UCAL_THURSDAY;
+ } else if (MatchPart(&pat, "friday")) {
+ index = UCAL_FRIDAY;
+ } else if (MatchPart(&pat, "saturday")) {
+ index = UCAL_SATURDAY;
+ } else if (MatchPart(&pat, "sunday")) {
+ index = UCAL_SUNDAY;
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+ } else if (MatchPart(&pat, "dayperiods")) {
+ if (!MatchPart(&pat, "/")) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ symbolType = UDAT_AM_PMS;
+
+ if (MatchPart(&pat, "am")) {
+ index = UCAL_AM;
+ } else if (MatchPart(&pat, "pm")) {
+ index = UCAL_PM;
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ // This part must be the final part with no trailing data.
+ if (*pat != '\0') {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ int32_t resultSize =
+ udat_getSymbols(fmt, symbolType, index, Char16ToUChar(chars.begin()),
+ INITIAL_CHAR_BUFFER_SIZE, &status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ if (!chars.resize(resultSize))
+ return false;
+ status = U_ZERO_ERROR;
+ udat_getSymbols(fmt, symbolType, index, Char16ToUChar(chars.begin()),
+ resultSize, &status);
+ }
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ JSString* word = NewStringCopyN<CanGC>(cx, chars.begin(), resultSize);
+ if (!word)
+ return false;
+
+ wordVal.setString(word);
+ } else {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_KEY, pattern.ptr());
+ return false;
+ }
+
+ MOZ_ASSERT(wordVal.isString());
+
+ // 5.b. Append the result string to result.
+ if (!DefineElement(cx, result, i, wordVal))
+ return false;
+ }
+
+ // 6. Return result.
+ args.rval().setObject(*result);
+ return true;
+}
+
/******************** Intl ********************/
const Class js::IntlClass = {
diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h
index 54764605b..b2197060d 100644
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -387,6 +387,48 @@ intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp);
+/**
+ * Returns an Array with CLDR-based fields display names.
+ * The function takes three arguments:
+ *
+ * locale
+ * BCP47 compliant locale string
+ * style
+ * A string with values: long or short or narrow
+ * keys
+ * An array or path-like strings that identify keys to be returned
+ * At the moment the following types of keys are supported:
+ *
+ * 'dates/fields/{year|month|week|day}'
+ * 'dates/gregorian/months/{january|...|december}'
+ * 'dates/gregorian/weekdays/{sunday|...|saturday}'
+ * 'dates/gregorian/dayperiods/{am|pm}'
+ *
+ * Example:
+ *
+ * let info = intl_ComputeDisplayNames(
+ * 'en-US',
+ * 'long',
+ * [
+ * 'dates/fields/year',
+ * 'dates/gregorian/months/january',
+ * 'dates/gregorian/weekdays/monday',
+ * 'dates/gregorian/dayperiods/am',
+ * ]
+ * );
+ *
+ * Returned value:
+ *
+ * [
+ * 'year',
+ * 'January',
+ * 'Monday',
+ * 'AM'
+ * ]
+ */
+extern MOZ_MUST_USE bool
+intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp);
+
#if ENABLE_INTL_API
/**
* Cast char16_t* strings to UChar* strings used by ICU.
@@ -402,6 +444,18 @@ Char16ToUChar(char16_t* chars)
{
return reinterpret_cast<UChar*>(chars);
}
+
+inline char16_t*
+UCharToChar16(UChar* chars)
+{
+ return reinterpret_cast<char16_t*>(chars);
+}
+
+inline const char16_t*
+UCharToChar16(const UChar* chars)
+{
+ return reinterpret_cast<const char16_t*>(chars);
+}
#endif // ENABLE_INTL_API
} // namespace js
diff --git a/js/src/js.msg b/js/src/js.msg
index 8d492f523..a276dab94 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -474,6 +474,8 @@ MSG_DEF(JSMSG_INTL_OBJECT_NOT_INITED, 3, JSEXN_TYPEERR, "Intl.{0}.prototype.{1}
MSG_DEF(JSMSG_INTL_OBJECT_REINITED, 0, JSEXN_TYPEERR, "can't initialize object twice as an object of an Intl constructor")
MSG_DEF(JSMSG_INVALID_CURRENCY_CODE, 1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}")
MSG_DEF(JSMSG_INVALID_DIGITS_VALUE, 1, JSEXN_RANGEERR, "invalid digits value: {0}")
+MSG_DEF(JSMSG_INVALID_KEYS_TYPE, 0, JSEXN_TYPEERR, "calendar info keys must be an object or undefined")
+MSG_DEF(JSMSG_INVALID_KEY, 1, JSEXN_RANGEERR, "invalid key: {0}")
MSG_DEF(JSMSG_INVALID_LANGUAGE_TAG, 1, JSEXN_RANGEERR, "invalid language tag: {0}")
MSG_DEF(JSMSG_INVALID_LOCALES_ELEMENT, 0, JSEXN_TYPEERR, "invalid element in locales argument")
MSG_DEF(JSMSG_INVALID_LOCALE_MATCHER, 1, JSEXN_RANGEERR, "invalid locale matcher in supportedLocalesOf(): {0}")
diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp
index ccaeda2a3..00a8abf84 100755
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -354,10 +354,22 @@ MakeDate(double day, double time)
JS_PUBLIC_API(double)
JS::MakeDate(double year, unsigned month, unsigned day)
{
+ MOZ_ASSERT(month <= 11);
+ MOZ_ASSERT(day >= 1 && day <= 31);
+
return ::MakeDate(MakeDay(year, month, day), 0);
}
JS_PUBLIC_API(double)
+JS::MakeDate(double year, unsigned month, unsigned day, double time)
+{
+ MOZ_ASSERT(month <= 11);
+ MOZ_ASSERT(day >= 1 && day <= 31);
+
+ return ::MakeDate(MakeDay(year, month, day), time);
+}
+
+JS_PUBLIC_API(double)
JS::YearFromTime(double time)
{
return ::YearFromTime(time);
diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp
index cc68c90d5..8d69ca942 100644
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -906,6 +906,7 @@ AddIntlExtras(JSContext* cx, unsigned argc, Value* vp)
static const JSFunctionSpec funcs[] = {
JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
+ JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
JS_FS_END
};
diff --git a/js/src/tests/Intl/getDisplayNames.js b/js/src/tests/Intl/getDisplayNames.js
new file mode 100644
index 000000000..ad2dbc940
--- /dev/null
+++ b/js/src/tests/Intl/getDisplayNames.js
@@ -0,0 +1,238 @@
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests the getCalendarInfo function with a diverse set of arguments.
+
+/*
+ * Test if getDisplayNames return value matches expected values.
+ */
+function checkDisplayNames(names, expected)
+{
+ assertEq(Object.getPrototypeOf(names), Object.prototype);
+
+ assertEq(names.locale, expected.locale);
+ assertEq(names.style, expected.style);
+
+ const nameValues = names.values;
+ const expectedValues = expected.values;
+
+ const nameValuesKeys = Object.getOwnPropertyNames(nameValues).sort();
+ const expectedValuesKeys = Object.getOwnPropertyNames(expectedValues).sort();
+
+ assertEqArray(nameValuesKeys, expectedValuesKeys);
+
+ for (let key of expectedValuesKeys)
+ assertEq(nameValues[key], expectedValues[key]);
+}
+
+addIntlExtras(Intl);
+
+let gDN = Intl.getDisplayNames;
+
+assertEq(gDN.length, 2);
+
+checkDisplayNames(gDN('en-US', {
+}), {
+ locale: 'en-US',
+ style: 'long',
+ values: {}
+});
+
+checkDisplayNames(gDN('en-US', {
+ keys: [
+ 'dates/gregorian/weekdays/wednesday'
+ ],
+ style: 'narrow'
+}), {
+ locale: 'en-US',
+ style: 'narrow',
+ values: {
+ 'dates/gregorian/weekdays/wednesday': 'W'
+ }
+});
+
+checkDisplayNames(gDN('en-US', {
+ keys: [
+ 'dates/fields/year',
+ 'dates/fields/month',
+ 'dates/fields/week',
+ 'dates/fields/day',
+ 'dates/gregorian/months/january',
+ 'dates/gregorian/months/february',
+ 'dates/gregorian/months/march',
+ 'dates/gregorian/weekdays/tuesday'
+ ]
+}), {
+ locale: 'en-US',
+ style: 'long',
+ values: {
+ 'dates/fields/year': 'year',
+ 'dates/fields/month': 'month',
+ 'dates/fields/week': 'week',
+ 'dates/fields/day': 'day',
+ 'dates/gregorian/months/january': 'January',
+ 'dates/gregorian/months/february': 'February',
+ 'dates/gregorian/months/march': 'March',
+ 'dates/gregorian/weekdays/tuesday': 'Tuesday',
+ }
+});
+
+checkDisplayNames(gDN('fr', {
+ keys: [
+ 'dates/fields/year',
+ 'dates/fields/day',
+ 'dates/gregorian/months/october',
+ 'dates/gregorian/weekdays/saturday',
+ 'dates/gregorian/dayperiods/pm'
+ ]
+}), {
+ locale: 'fr',
+ style: 'long',
+ values: {
+ 'dates/fields/year': 'année',
+ 'dates/fields/day': 'jour',
+ 'dates/gregorian/months/october': 'octobre',
+ 'dates/gregorian/weekdays/saturday': 'samedi',
+ 'dates/gregorian/dayperiods/pm': 'PM'
+ }
+});
+
+checkDisplayNames(gDN('it', {
+ style: 'short',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ 'dates/gregorian/months/august',
+ 'dates/gregorian/dayperiods/am',
+ 'dates/fields/month',
+ ]
+}), {
+ locale: 'it',
+ style: 'short',
+ values: {
+ 'dates/gregorian/weekdays/thursday': 'gio',
+ 'dates/gregorian/months/august': 'ago',
+ 'dates/gregorian/dayperiods/am': 'AM',
+ 'dates/fields/month': 'mese'
+ }
+});
+
+checkDisplayNames(gDN('ar', {
+ style: 'long',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ 'dates/gregorian/months/august',
+ 'dates/gregorian/dayperiods/am',
+ 'dates/fields/month',
+ ]
+}), {
+ locale: 'ar',
+ style: 'long',
+ values: {
+ 'dates/gregorian/weekdays/thursday': 'الخميس',
+ 'dates/gregorian/months/august': 'أغسطس',
+ 'dates/gregorian/dayperiods/am': 'ص',
+ 'dates/fields/month': 'الشهر'
+ }
+});
+
+/* Invalid input */
+
+assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ style: '',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ ]
+ });
+}, RangeError);
+
+assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ style: 'bogus',
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ ]
+ });
+}, RangeError);
+
+assertThrowsInstanceOf(() => {
+ gDN('foo-X', {
+ keys: [
+ 'dates/gregorian/weekdays/thursday',
+ ]
+ });
+}, RangeError);
+
+const typeErrorKeys = [
+ null,
+ 'string',
+ Symbol.iterator,
+ 15,
+ 1,
+ 3.7,
+ NaN,
+ Infinity
+];
+
+for (let keys of typeErrorKeys) {
+ assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ keys
+ });
+ }, TypeError);
+}
+
+const rangeErrorKeys = [
+ [''],
+ ['foo'],
+ ['dates/foo'],
+ ['/dates/foo'],
+ ['dates/foo/foo'],
+ ['dates/fields'],
+ ['dates/fields/'],
+ ['dates/fields/foo'],
+ ['dates/fields/foo/month'],
+ ['/dates/foo/faa/bar/baz'],
+ ['dates///bar/baz'],
+ ['dates/gregorian'],
+ ['dates/gregorian/'],
+ ['dates/gregorian/foo'],
+ ['dates/gregorian/months'],
+ ['dates/gregorian/months/foo'],
+ ['dates/gregorian/weekdays'],
+ ['dates/gregorian/weekdays/foo'],
+ ['dates/gregorian/dayperiods'],
+ ['dates/gregorian/dayperiods/foo'],
+ ['dates/gregorian/months/الشهر'],
+ [3],
+ [null],
+ ['d', 'a', 't', 'e', 's'],
+ ['datesEXTRA'],
+ ['dates/fieldsEXTRA'],
+ ['dates/gregorianEXTRA'],
+ ['dates/gregorian/monthsEXTRA'],
+ ['dates/gregorian/weekdaysEXTRA'],
+ ['dates/fields/dayperiods/amEXTRA'],
+ ['dates/gregori\u1161n/months/january'],
+ ["dates/fields/year/"],
+ ["dates/fields/month/"],
+ ["dates/fields/week/"],
+ ["dates/fields/day/"],
+ ["dates/gregorian/months/january/"],
+ ["dates/gregorian/weekdays/saturday/"],
+ ["dates/gregorian/dayperiods/am/"],
+ ["dates/fields/months/january/"],
+];
+
+for (let keys of rangeErrorKeys) {
+ assertThrowsInstanceOf(() => {
+ gDN('en-US', {
+ keys
+ });
+ }, RangeError);
+}
+
+if (typeof reportCompare === 'function')
+ reportCompare(0, 0);
diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index bf49f2268..9a8ec7679 100644
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2477,6 +2477,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
+ JS_FN("intl_ComputeDisplayNames", intl_ComputeDisplayNames, 3,0),
JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
index a118c38f9..f8c7f52a9 100644
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -3658,13 +3658,13 @@ nsCSSFrameConstructor::FindInputData(Element* aElement,
nsCSSAnonBoxes::buttonContent) },
// TODO: this is temporary until a frame is written: bug 635240.
SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewNumberControlFrame),
- // TODO: this is temporary until a frame is written: bug 888320.
- SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
// On Android/B2G, date/time input appears as a normal text box.
SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
#else
SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewDateTimeControlFrame),
+ SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewDateTimeControlFrame),
#endif
// TODO: this is temporary until a frame is written: bug 888320
SIMPLE_INT_CREATE(NS_FORM_INPUT_MONTH, NS_NewTextControlFrame),
diff --git a/layout/forms/nsDateTimeControlFrame.cpp b/layout/forms/nsDateTimeControlFrame.cpp
index df2e43986..fa22dceba 100644
--- a/layout/forms/nsDateTimeControlFrame.cpp
+++ b/layout/forms/nsDateTimeControlFrame.cpp
@@ -372,7 +372,8 @@ nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
auto contentAsInputElem = static_cast<dom::HTMLInputElement*>(mContent);
// If script changed the <input>'s type before setting these attributes
// then we don't need to do anything since we are going to be reframed.
- if (contentAsInputElem->GetType() == NS_FORM_INPUT_TIME) {
+ if (contentAsInputElem->GetType() == NS_FORM_INPUT_TIME ||
+ contentAsInputElem->GetType() == NS_FORM_INPUT_DATE) {
if (aAttribute == nsGkAtoms::value) {
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
do_QueryInterface(mInputAreaContent);
diff --git a/layout/reftests/forms/input/datetime/reftest.list b/layout/reftests/forms/input/datetime/reftest.list
index 0ce2002bd..a62d56c7c 100644
--- a/layout/reftests/forms/input/datetime/reftest.list
+++ b/layout/reftests/forms/input/datetime/reftest.list
@@ -11,3 +11,14 @@ skip-if(!Android&&!B2G&&!Mulet) == time-simple-unthemed.html time-simple-untheme
# type change
skip-if(Android||B2G||Mulet) == to-time-from-other-type-unthemed.html time-simple-unthemed.html
skip-if(Android||B2G||Mulet) == from-time-to-other-type-unthemed.html from-time-to-other-type-unthemed-ref.html
+
+# content should not overflow on small width/height
+skip-if(Android) == time-small-width.html time-small-width-ref.html
+skip-if(Android) == time-small-height.html time-small-height-ref.html
+skip-if(Android) == time-small-width-height.html time-small-width-height-ref.html
+
+# content (text) should be left aligned
+skip-if(Android) == time-content-left-aligned.html time-content-left-aligned-ref.html
+
+# reset button should be right aligned
+skip-if(Android) fails-if(styloVsGecko) == time-reset-button-right-aligned.html time-reset-button-right-aligned-ref.html # bug 1372062
diff --git a/layout/reftests/forms/input/datetime/time-content-left-aligned-ref.html b/layout/reftests/forms/input/datetime/time-content-left-aligned-ref.html
new file mode 100644
index 000000000..ad8be9adc
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-content-left-aligned-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input type="time" style="width: 200px;">
+ <!-- div to cover the right area -->
+ <div style="display:block; position:absolute; background-color:black;
+ top:0px; left:40px; width:200px; height:100px;"></div>
+ </body>
+</html>
diff --git a/layout/reftests/forms/input/datetime/time-content-left-aligned.html b/layout/reftests/forms/input/datetime/time-content-left-aligned.html
new file mode 100644
index 000000000..aa910cddf
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-content-left-aligned.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input type="time" style="width: 50px;">
+ <!-- div to cover the right area -->
+ <div style="display:block; position:absolute; background-color:black;
+ top:0px; left:40px; width:200px; height:100px;"></div>
+ </body>
+</html>
diff --git a/layout/reftests/forms/input/datetime/time-reset-button-right-aligned-ref.html b/layout/reftests/forms/input/datetime/time-reset-button-right-aligned-ref.html
new file mode 100644
index 000000000..3d36f2068
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-reset-button-right-aligned-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input type="time" value="10:00" style="float: right; color: white;">
+ <!-- div to cover the left area -->
+ <div style="display:block; position:absolute; background-color:black;
+ top:0px; right:30px; width:500px; height:100px;"></div>
+ </body>
+</html>
diff --git a/layout/reftests/forms/input/datetime/time-reset-button-right-aligned.html b/layout/reftests/forms/input/datetime/time-reset-button-right-aligned.html
new file mode 100644
index 000000000..72d5cc140
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-reset-button-right-aligned.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input type="time" value="10:00" style="width: 150px; float: right;
+ color: white;">
+ <!-- div to cover the left area -->
+ <div style="display:block; position:absolute; background-color:black;
+ top:0px; right:30px; width:500px; height:100px;"></div>
+ </body>
+</html>
diff --git a/layout/reftests/forms/input/datetime/time-small-height-ref.html b/layout/reftests/forms/input/datetime/time-small-height-ref.html
new file mode 100644
index 000000000..fcda93df9
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-height-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+input {
+ width: 200px;
+ height: 5px;
+ outline: 1px dotted black;
+ /* Disable baseline alignment, so that our y-position isn't influenced by the
+ * choice of font inside of input: */
+ vertical-align: top;
+}
+ </style>
+ </head>
+ <body>
+ <input>
+ </body>
+</html>
diff --git a/layout/reftests/forms/input/datetime/time-small-height.html b/layout/reftests/forms/input/datetime/time-small-height.html
new file mode 100644
index 000000000..3044822fe
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-height.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+input {
+ width: 200px;
+ height: 5px;
+ outline: 1px dotted black;
+ color: white;
+ /* Disable baseline alignment, so that our y-position isn't influenced by the
+ * choice of font inside of input: */
+ vertical-align: top;
+}
+ </style>
+ </head>
+ <body>
+ <input type="time">
+ </body>
+</html>
diff --git a/layout/reftests/forms/input/datetime/time-small-width-height-ref.html b/layout/reftests/forms/input/datetime/time-small-width-height-ref.html
new file mode 100644
index 000000000..0979243db
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-width-height-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+input {
+ width: 8px;
+ height: 8px;
+ outline: 1px dotted black;
+ /* Disable baseline alignment, so that our y-position isn't influenced by the
+ * choice of font inside of input: */
+ vertical-align: top;
+}
+ </style>
+ </head>
+ <body>
+ <input>
+ </body>
+</html>
diff --git a/layout/reftests/forms/input/datetime/time-small-width-height.html b/layout/reftests/forms/input/datetime/time-small-width-height.html
new file mode 100644
index 000000000..a221b2819
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-width-height.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+input {
+ width: 8px;
+ height: 8px;
+ outline: 1px dotted black;
+ color: white;
+ /* Disable baseline alignment, so that our y-position isn't influenced by the
+ * choice of font inside of input: */
+ vertical-align: top;
+}
+ </style>
+ </head>
+ <body>
+ <input type="time">
+ </body>
+</html>
diff --git a/layout/reftests/forms/input/datetime/time-small-width-ref.html b/layout/reftests/forms/input/datetime/time-small-width-ref.html
new file mode 100644
index 000000000..2379c7080
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-width-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+input {
+ width: 10px;
+ height: 1.5em;
+ outline: 1px dotted black;
+ background: white;
+ /* Disable baseline alignment, so that our y-position isn't influenced by the
+ * choice of font inside of input: */
+ vertical-align: top;
+}
+ </style>
+ </head>
+ <body>
+ <input>
+ </body>
+</html>
diff --git a/layout/reftests/forms/input/datetime/time-small-width.html b/layout/reftests/forms/input/datetime/time-small-width.html
new file mode 100644
index 000000000..f76f7fdfa
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-width.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+input {
+ width: 10px;
+ height: 1.5em;
+ outline: 1px dotted black;
+ color: white;
+ background: white;
+ /* Disable baseline alignment, so that our y-position isn't influenced by the
+ * choice of font inside of input: */
+ vertical-align: top;
+}
+ </style>
+ </head>
+ <body>
+ <input type="time">
+ </body>
+</html>
diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css
index f045540b1..e7566e183 100644
--- a/layout/style/res/forms.css
+++ b/layout/style/res/forms.css
@@ -1135,3 +1135,8 @@ input[type="number"] > div > div > div:hover {
/* give some indication of hover state for the up/down buttons */
background-color: lightblue;
}
+
+input[type="date"],
+input[type="time"] {
+ overflow: hidden !important;
+}
diff --git a/layout/style/res/html.css b/layout/style/res/html.css
index a779461de..bc3f08210 100644
--- a/layout/style/res/html.css
+++ b/layout/style/res/html.css
@@ -774,6 +774,11 @@ input[type="time"] > xul|datetimebox {
-moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input");
}
+input[type="date"] > xul|datetimebox {
+ display: flex;
+ -moz-binding: url("chrome://global/content/bindings/datetimebox.xml#date-input");
+}
+
/* details & summary */
/* Need to revert Bug 1259889 Part 2 when removing details preference. */
@supports -moz-bool-pref("dom.details_element.enabled") {
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 59f8de9ac..e3e23550a 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1179,10 +1179,13 @@ pref("dom.forms.number", true);
// platforms which don't have a color picker implemented yet.
pref("dom.forms.color", true);
-// Support for input type=date, time, month, week and datetime-local. By
-// default, disabled.
+// Support for input type=date and type=time. By default, disabled.
pref("dom.forms.datetime", false);
+// Support for input type=month, type=week and type=datetime-local. By default,
+// disabled.
+pref("dom.forms.datetime.others", false);
+
// Enable time picker UI. By default, disabled.
pref("dom.forms.datetime.timepicker", false);
diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp
index bc1350f28..21c4cf7fd 100644
--- a/netwerk/base/nsStandardURL.cpp
+++ b/netwerk/base/nsStandardURL.cpp
@@ -781,11 +781,13 @@ nsStandardURL::BuildNormalizedSpec(const char *spec)
i = AppendSegmentToBuf(buf, i, spec, username, mUsername,
&encUsername, useEncUsername, &diff);
ShiftFromPassword(diff);
- if (password.mLen >= 0) {
+ if (password.mLen > 0) {
buf[i++] = ':';
i = AppendSegmentToBuf(buf, i, spec, password, mPassword,
&encPassword, useEncPassword, &diff);
ShiftFromHost(diff);
+ } else {
+ mPassword.mLen = -1;
}
buf[i++] = '@';
}
@@ -1483,6 +1485,11 @@ nsStandardURL::SetSpec(const nsACString &input)
rv = BuildNormalizedSpec(spec);
}
+ // Make sure that a URLTYPE_AUTHORITY has a non-empty hostname.
+ if (mURLType == URLTYPE_AUTHORITY && mHost.mLen == -1) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+
if (NS_FAILED(rv)) {
Clear();
// If parsing the spec has failed, restore the old URL
@@ -1616,7 +1623,7 @@ nsStandardURL::SetUserPass(const nsACString &input)
usernameLen),
esc_Username | esc_AlwaysCopy,
buf, ignoredOut);
- if (passwordLen >= 0) {
+ if (passwordLen > 0) {
buf.Append(':');
passwordLen = encoder.EncodeSegmentCount(userpass.get(),
URLSegment(passwordPos,
@@ -1624,6 +1631,8 @@ nsStandardURL::SetUserPass(const nsACString &input)
esc_Password |
esc_AlwaysCopy, buf,
ignoredOut);
+ } else {
+ passwordLen = -1;
}
if (mUsername.mLen < 0)
buf.Append('@');
@@ -1654,8 +1663,10 @@ nsStandardURL::SetUserPass(const nsACString &input)
// update positions and lengths
mUsername.mLen = usernameLen;
mPassword.mLen = passwordLen;
- if (passwordLen)
+ if (passwordLen > 0) {
mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ }
+
return NS_OK;
}
@@ -3092,20 +3103,26 @@ nsStandardURL::SetFile(nsIFile *file)
rv = net_GetURLSpecFromFile(file, url);
if (NS_FAILED(rv)) return rv;
- SetSpec(url);
+ uint32_t oldURLType = mURLType;
+ uint32_t oldDefaultPort = mDefaultPort;
+ rv = Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, url, nullptr, nullptr);
- rv = Init(mURLType, mDefaultPort, url, nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ // Restore the old url type and default port if the call to Init fails.
+ mURLType = oldURLType;
+ mDefaultPort = oldDefaultPort;
+ return rv;
+ }
// must clone |file| since its value is not guaranteed to remain constant
- if (NS_SUCCEEDED(rv)) {
- InvalidateCache();
- if (NS_FAILED(file->Clone(getter_AddRefs(mFile)))) {
- NS_WARNING("nsIFile::Clone failed");
- // failure to clone is not fatal (GetFile will generate mFile)
- mFile = nullptr;
- }
+ InvalidateCache();
+ if (NS_FAILED(file->Clone(getter_AddRefs(mFile)))) {
+ NS_WARNING("nsIFile::Clone failed");
+ // failure to clone is not fatal (GetFile will generate mFile)
+ mFile = nullptr;
}
- return rv;
+
+ return NS_OK;
}
//----------------------------------------------------------------------------
diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h
index d51662db9..35b14a511 100644
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -489,8 +489,8 @@ private:
nsCString mCompatGecko;
bool mCompatGeckoEnabled;
nsCString mCompatFirefox;
- nsCString mCompatFirefoxVersion;
bool mCompatFirefoxEnabled;
+ nsCString mCompatFirefoxVersion;
nsXPIDLCString mCompatDevice;
nsCString mDeviceModelId;
diff --git a/netwerk/test/unit/test_URIs.js b/netwerk/test/unit/test_URIs.js
index b68c4f787..1cad7768f 100644
--- a/netwerk/test/unit/test_URIs.js
+++ b/netwerk/test/unit/test_URIs.js
@@ -92,18 +92,6 @@ var gTests = [
ref: "",
relativeURI: "data/text/plain,2",
nsIURL: true, nsINestedURI: false },
- { spec: "ftp://",
- scheme: "ftp",
- prePath: "ftp://",
- path: "/",
- ref: "",
- nsIURL: true, nsINestedURI: false },
- { spec: "ftp:///",
- scheme: "ftp",
- prePath: "ftp://",
- path: "/",
- ref: "",
- nsIURL: true, nsINestedURI: false },
{ spec: "ftp://ftp.mozilla.org/pub/mozilla.org/README",
scheme: "ftp",
prePath: "ftp://ftp.mozilla.org",
@@ -121,7 +109,7 @@ var gTests = [
nsIURL: true, nsINestedURI: false },
{ spec: "ftp://foo:@ftp.mozilla.org:100/pub/mozilla.org/README",
scheme: "ftp",
- prePath: "ftp://foo:@ftp.mozilla.org:100",
+ prePath: "ftp://foo@ftp.mozilla.org:100",
port: 100,
username: "foo",
password: "",
@@ -135,18 +123,6 @@ var gTests = [
path: "//mozilla.org/",
ref: "",
nsIURL: false, nsINestedURI: false },
- { spec: "http://",
- scheme: "http",
- prePath: "http://",
- path: "/",
- ref: "",
- nsIURL: true, nsINestedURI: false },
- { spec: "http:///",
- scheme: "http",
- prePath: "http://",
- path: "/",
- ref: "",
- nsIURL: true, nsINestedURI: false },
{ spec: "http://www.example.com/",
scheme: "http",
prePath: "http://www.example.com",
diff --git a/netwerk/test/unit/test_standardurl.js b/netwerk/test/unit/test_standardurl.js
index c4d44f41f..4cc2f393e 100644
--- a/netwerk/test/unit/test_standardurl.js
+++ b/netwerk/test/unit/test_standardurl.js
@@ -251,6 +251,17 @@ add_test(function test_escapeBrackets()
run_next_test();
});
+add_test(function test_escapeQuote()
+{
+ var url = stringToURL("http://example.com/#'");
+ do_check_eq(url.spec, "http://example.com/#'");
+ do_check_eq(url.ref, "'");
+ url.ref = "test'test";
+ do_check_eq(url.spec, "http://example.com/#test'test");
+ do_check_eq(url.ref, "test'test");
+ run_next_test();
+});
+
add_test(function test_apostropheEncoding()
{
// For now, single quote is escaped everywhere _except_ the path.
@@ -335,6 +346,14 @@ add_test(function test_backslashReplacement()
run_next_test();
});
+add_test(function test_authority_host()
+{
+ Assert.throws(() => { stringToURL("http:"); }, "TYPE_AUTHORITY should have host");
+ Assert.throws(() => { stringToURL("http:///"); }, "TYPE_AUTHORITY should have host");
+
+ run_next_test();
+});
+
add_test(function test_trim_C0_and_space()
{
var url = stringToURL("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f http://example.com/ \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f ");
@@ -453,3 +472,23 @@ add_test(function test_invalidHostChars() {
// hostname separators, so there is no way to set them and fail.
run_next_test();
});
+
+add_test(function test_emptyPassword() {
+ var url = stringToURL("http://a:@example.com");
+ do_check_eq(url.spec, "http://a@example.com/");
+ url.password = "pp";
+ do_check_eq(url.spec, "http://a:pp@example.com/");
+ url.password = "";
+ do_check_eq(url.spec, "http://a@example.com/");
+ url.userPass = "xxx:";
+ do_check_eq(url.spec, "http://xxx@example.com/");
+ url.password = "zzzz";
+ do_check_eq(url.spec, "http://xxx:zzzz@example.com/");
+ url.userPass = "xxxxx:yyyyyy";
+ do_check_eq(url.spec, "http://xxxxx:yyyyyy@example.com/");
+ url.userPass = "z:";
+ do_check_eq(url.spec, "http://z@example.com/");
+ url.password = "ppppppppppp";
+ do_check_eq(url.spec, "http://z:ppppppppppp@example.com/");
+ run_next_test();
+});
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js b/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js
index 244b79a5e..710a65ee5 100644
--- a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js
@@ -143,7 +143,7 @@ add_test(function serverErrorResponse () {
add_test(function networkErrorResponse () {
let client = new FxAccountsOAuthGrantClient({
- serverURL: "http://",
+ serverURL: "http://domain.dummy",
client_id: "abc123"
});
Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
diff --git a/services/fxaccounts/tests/xpcshell/test_profile_client.js b/services/fxaccounts/tests/xpcshell/test_profile_client.js
index 2243da3aa..20ff6efc6 100644
--- a/services/fxaccounts/tests/xpcshell/test_profile_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_profile_client.js
@@ -268,7 +268,7 @@ add_test(function server401ResponsePersists () {
add_test(function networkErrorResponse () {
let client = new FxAccountsProfileClient({
- serverURL: "http://",
+ serverURL: "http://domain.dummy",
fxa: mockFxa,
});
client.fetchProfile()
diff --git a/testing/marionette/interaction.js b/testing/marionette/interaction.js
index c8275665d..2392485d7 100644
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -76,6 +76,30 @@ const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([
"TAB",
]);
+/**
+ * Common form controls that user can change the value property interactively.
+ */
+const COMMON_FORM_CONTROLS = new Set([
+ "input",
+ "textarea",
+ "select",
+]);
+
+/**
+ * Input elements that do not fire "input" and "change" events when value
+ * property changes.
+ */
+const INPUT_TYPES_NO_EVENT = new Set([
+ "checkbox",
+ "radio",
+ "file",
+ "hidden",
+ "image",
+ "reset",
+ "button",
+ "submit",
+]);
+
this.interaction = {};
/**
@@ -339,6 +363,32 @@ interaction.uploadFile = function (el, path) {
};
/**
+ * Sets a form element's value.
+ *
+ * @param {DOMElement} el
+ * An form element, e.g. input, textarea, etc.
+ * @param {string} value
+ * The value to be set.
+ *
+ * @throws TypeError
+ * If |el| is not an supported form element.
+ */
+interaction.setFormControlValue = function* (el, value) {
+ if (!COMMON_FORM_CONTROLS.has(el.localName)) {
+ throw new TypeError("This function is for form elements only");
+ }
+
+ el.value = value;
+
+ if (INPUT_TYPES_NO_EVENT.has(el.type)) {
+ return;
+ }
+
+ event.input(el);
+ event.change(el);
+};
+
+/**
* Send keys to element.
*
* @param {DOMElement|XULElement} el
diff --git a/testing/marionette/listener.js b/testing/marionette/listener.js
index b64eb378d..619ac249d 100644
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -30,6 +30,7 @@ Cu.import("chrome://marionette/content/session.js");
Cu.import("chrome://marionette/content/simpletest.js");
Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -1465,6 +1466,9 @@ function* sendKeysToElement(id, val) {
if (el.type == "file") {
let path = val.join("");
yield interaction.uploadFile(el, path);
+ } else if ((el.type == "date" || el.type == "time") &&
+ Preferences.get("dom.forms.datetime")) {
+ yield interaction.setFormControlValue(el, val);
} else {
yield interaction.sendKeysToElement(
el, val, false, capabilities.get("moz:accessibilityChecks"));
diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js
index 91218b5f3..ac2c1e077 100644
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -12,6 +12,7 @@ user_pref("dom.experimental_forms", true); // on for testing
user_pref("dom.forms.number", true); // on for testing
user_pref("dom.forms.color", true); // on for testing
user_pref("dom.forms.datetime", true); // on for testing
+user_pref("dom.forms.datetime.others", true); // on for testing
user_pref("dom.max_script_run_time", 0); // no slow script dialogs
user_pref("hangmonitor.timeout", 0); // no hang monitor
user_pref("dom.max_chrome_script_run_time", 0);
diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json
index 65626d6b8..ca574833b 100644
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -39138,6 +39138,18 @@
"url": "/2dcontext/drawing-images-to-the-canvas/drawimage_html_image_9.html"
}
],
+ "2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1.html": [
+ {
+ "path": "2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1.html",
+ "references": [
+ [
+ "/2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1_ref.html",
+ "=="
+ ]
+ ],
+ "url": "/2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1.html"
+ }
+ ],
"2dcontext/line-styles/canvas_linestyles_linecap_001.htm": [
{
"path": "2dcontext/line-styles/canvas_linestyles_linecap_001.htm",
diff --git a/testing/web-platform/meta/XMLHttpRequest/open-url-bogus.htm.ini b/testing/web-platform/meta/XMLHttpRequest/open-url-bogus.htm.ini
deleted file mode 100644
index ef7b5d910..000000000
--- a/testing/web-platform/meta/XMLHttpRequest/open-url-bogus.htm.ini
+++ /dev/null
@@ -1,11 +0,0 @@
-[open-url-bogus.htm]
- type: testharness
- [XMLHttpRequest: open() - bogus URLs (http:)]
- expected: FAIL
-
- [XMLHttpRequest: open() - bogus URLs (ftp:)]
- expected: FAIL
-
- [XMLHttpRequest: open() - bogus URLs (http:////////////)]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-checkValidity.html.ini b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-checkValidity.html.ini
index a8247d5a0..23bd8642c 100644
--- a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-checkValidity.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-checkValidity.html.ini
@@ -32,25 +32,3 @@
[[INPUT in DATETIME status\] The datetime type must be supported.]
expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The datetime-local type must be supported.]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from an overflow]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from an overflow (in a form)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from an underflow]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from an underflow (in a form)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from a step mismatch]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from a step mismatch (in a form)]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-reportValidity.html.ini b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-reportValidity.html.ini
index 223667997..5b373cfee 100644
--- a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-reportValidity.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-reportValidity.html.ini
@@ -39,24 +39,3 @@
[[INPUT in DATETIME status\] The datetime type must be supported.]
expected: FAIL
- [[INPUT in DATETIME-LOCAL status\] The datetime-local type must be supported.]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from an overflow]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from an overflow (in a form)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from an underflow]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from an underflow (in a form)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from a step mismatch]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] suffering from a step mismatch (in a form)]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.html.ini b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.html.ini
index 6af2a360e..d8c650632 100644
--- a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeOverflow.html.ini
@@ -3,21 +3,3 @@
[[INPUT in DATETIME status\] The datetime type must be supported.]
expected: FAIL
- [[INPUT in DATETIME-LOCAL status\] The datetime-local type must be supported.]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is greater than max]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is greater than max(with millisecond in 1 digit)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is greater than max(with millisecond in 2 digits)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is greater than max(with millisecond in 3 digits)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is greater than max(Year is 10000 should be valid)]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.html.ini b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.html.ini
index 344ee0039..87f6f964e 100644
--- a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-rangeUnderflow.html.ini
@@ -3,21 +3,3 @@
[[INPUT in DATETIME status\] The datetime type must be supported.]
expected: FAIL
- [[INPUT in DATETIME-LOCAL status\] The datetime-local type must be supported.]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is less than min]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is less than min(with millisecond in 1 digit)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is less than min(with millisecond in 2 digits)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is less than min(with millisecond in 3 digits)]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value is less than min(Year is 10000 should be valid)]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-stepMismatch.html.ini b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-stepMismatch.html.ini
index 0c33bdcbe..527760e60 100644
--- a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-stepMismatch.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-stepMismatch.html.ini
@@ -2,7 +2,3 @@
type: testharness
[[INPUT in DATETIME status\] The datetime type must be supported.]
expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The value must mismatch the step]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-valid.html.ini b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-valid.html.ini
index 1cddcd033..8eb7940d9 100644
--- a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-valid.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-validity-valid.html.ini
@@ -23,16 +23,3 @@
[[INPUT in MONTH status\] validity.valid must be false if validity.stepMismatch is true]
expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] The datetime-local type must be supported.]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] validity.valid must be false if validity.rangeOverflow is true]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] validity.valid must be false if validity.rangeUnderflow is true]
- expected: FAIL
-
- [[INPUT in DATETIME-LOCAL status\] validity.valid must be false if validity.stepMismatch is true]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/html/semantics/selectors/pseudo-classes/inrange-outofrange.html.ini b/testing/web-platform/meta/html/semantics/selectors/pseudo-classes/inrange-outofrange.html.ini
deleted file mode 100644
index 5e2525ac2..000000000
--- a/testing/web-platform/meta/html/semantics/selectors/pseudo-classes/inrange-outofrange.html.ini
+++ /dev/null
@@ -1,20 +0,0 @@
-[inrange-outofrange.html]
- type: testharness
- [':in-range' matches all elements that are candidates for constraint validation, have range limitations, and that are neither suffering from an underflow nor suffering from an overflow]
- expected: FAIL
-
- [':in-range' update number1's value < min]
- expected: FAIL
-
- [':in-range' update number3's min < value]
- expected: FAIL
-
- [':out-of-range' matches all elements that are candidates for constraint validation, have range limitations, and that are either suffering from an underflow or suffering from an overflow]
- expected: FAIL
-
- [':out-of-range' update number1's value < min]
- expected: FAIL
-
- [':out-of-range' update number3's min < value]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/url/url-constructor.html.ini b/testing/web-platform/meta/url/url-constructor.html.ini
index 6da03043b..22fddbc15 100644
--- a/testing/web-platform/meta/url/url-constructor.html.ini
+++ b/testing/web-platform/meta/url/url-constructor.html.ini
@@ -219,6 +219,3 @@
[Parsing: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank>]
expected: FAIL
- [Parsing: <http:> against <https://example.org/foo/bar>]
- expected: FAIL
-
diff --git a/testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_html_image_1.html b/testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1.html
index b9de85a97..74a00e037 100644
--- a/testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_html_image_1.html
+++ b/testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1.html
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<meta charset="utf-8">
-<link rel=match href=drawimage_html_image_1_ref.html>
+<link rel=match href=drawimage_svg_image_1_ref.html>
<style>
html, body {
margin: 0;
@@ -13,8 +13,8 @@ var sourceWidth = 100;
var sourceHeight = 100;
var smoothingEnabled = false;
var destCanvas = document.getElementById('dest');
-var sourceImg = document.createElement('img');
-sourceImg.src = '../2x2.png'
+var sourceImg = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+sourceImg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '../2x2.png');
sourceImg.width = sourceWidth;
sourceImg.height = sourceHeight;
diff --git a/testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_html_image_1_ref.html b/testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1_ref.html
index 60545df17..60545df17 100644
--- a/testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_html_image_1_ref.html
+++ b/testing/web-platform/tests/2dcontext/drawing-images-to-the-canvas/drawimage_svg_image_1_ref.html
diff --git a/toolkit/components/mozintl/MozIntl.cpp b/toolkit/components/mozintl/MozIntl.cpp
index 9c393c296..9c61c73a6 100644
--- a/toolkit/components/mozintl/MozIntl.cpp
+++ b/toolkit/components/mozintl/MozIntl.cpp
@@ -48,6 +48,32 @@ MozIntl::AddGetCalendarInfo(JS::Handle<JS::Value> val, JSContext* cx)
return NS_OK;
}
+NS_IMETHODIMP
+MozIntl::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
+{
+ if (!val.isObject()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JS::Rooted<JSObject*> realIntlObj(cx, js::CheckedUnwrap(&val.toObject()));
+ if (!realIntlObj) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JSAutoCompartment ac(cx, realIntlObj);
+
+ static const JSFunctionSpec funcs[] = {
+ JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
+ JS_FS_END
+ };
+
+ if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntl)
NS_DEFINE_NAMED_CID(MOZ_MOZINTL_CID);
diff --git a/toolkit/components/mozintl/mozIMozIntl.idl b/toolkit/components/mozintl/mozIMozIntl.idl
index 67be184d4..f28824d47 100644
--- a/toolkit/components/mozintl/mozIMozIntl.idl
+++ b/toolkit/components/mozintl/mozIMozIntl.idl
@@ -9,4 +9,5 @@
interface mozIMozIntl : nsISupports
{
[implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
+ [implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
};
diff --git a/toolkit/components/mozintl/test/test_mozintl.js b/toolkit/components/mozintl/test/test_mozintl.js
index 0eca2c67e..8d2720bf0 100644
--- a/toolkit/components/mozintl/test/test_mozintl.js
+++ b/toolkit/components/mozintl/test/test_mozintl.js
@@ -7,6 +7,7 @@ function run_test() {
test_this_global(mozIntl);
test_cross_global(mozIntl);
+ test_methods_presence(mozIntl);
ok(true);
}
@@ -30,3 +31,16 @@ function test_cross_global(mozIntl) {
equal(waivedX.getCalendarInfo() instanceof Object, false);
equal(waivedX.getCalendarInfo() instanceof global.Object, true);
}
+
+function test_methods_presence(mozIntl) {
+ equal(mozIntl.addGetCalendarInfo instanceof Function, true);
+ equal(mozIntl.addGetDisplayNames instanceof Function, true);
+
+ let x = {};
+
+ mozIntl.addGetCalendarInfo(x);
+ equal(x.getCalendarInfo instanceof Function, true);
+
+ mozIntl.addGetDisplayNames(x);
+ equal(x.getDisplayNames instanceof Function, true);
+}
diff --git a/toolkit/components/passwordmgr/test/unit/test_logins_search.js b/toolkit/components/passwordmgr/test/unit/test_logins_search.js
index 188c75039..730771981 100644
--- a/toolkit/components/passwordmgr/test/unit/test_logins_search.js
+++ b/toolkit/components/passwordmgr/test/unit/test_logins_search.js
@@ -192,7 +192,6 @@ add_task(function test_search_all_full_case_sensitive()
{
checkAllSearches({ hostname: "http://www.example.com" }, 1);
checkAllSearches({ hostname: "http://www.example.com/" }, 0);
- checkAllSearches({ hostname: "http://" }, 0);
checkAllSearches({ hostname: "example.com" }, 0);
checkAllSearches({ formSubmitURL: "http://www.example.com" }, 2);
diff --git a/toolkit/components/places/UnifiedComplete.js b/toolkit/components/places/UnifiedComplete.js
index ad3d35aab..acd358b11 100644
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -1245,7 +1245,7 @@ Search.prototype = {
// * If the protocol differs we should not match. For example if the user
// searched https we should not return http.
try {
- let prefixURI = NetUtil.newURI(this._strippedPrefix);
+ let prefixURI = NetUtil.newURI(this._strippedPrefix + match.token);
let finalURI = NetUtil.newURI(match.url);
if (prefixURI.scheme != finalURI.scheme)
return false;
diff --git a/toolkit/components/places/nsNavHistory.cpp b/toolkit/components/places/nsNavHistory.cpp
index 8cf3a2e32..7f4007c1a 100644
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -949,6 +949,10 @@ nsresult // static
nsNavHistory::AsciiHostNameFromHostString(const nsACString& aHostName,
nsACString& aAscii)
{
+ aAscii.Truncate();
+ if (aHostName.IsEmpty()) {
+ return NS_OK;
+ }
// To properly generate a uri we must provide a protocol.
nsAutoCString fakeURL("http://");
fakeURL.Append(aHostName);
diff --git a/toolkit/components/satchel/test/test_form_autocomplete.html b/toolkit/components/satchel/test/test_form_autocomplete.html
index 4cf09117a..d2c22a3db 100644
--- a/toolkit/components/satchel/test/test_form_autocomplete.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete.html
@@ -172,7 +172,7 @@ function setupFormHistory(aCallback) {
{ op : "add", fieldname : "field8", value : "value" },
{ op : "add", fieldname : "field9", value : "value" },
{ op : "add", fieldname : "field10", value : "42" },
- { op : "add", fieldname : "field11", value : "2010-10-10" },
+ { op : "add", fieldname : "field11", value : "2010-10-10" }, // not used, since type=date doesn't have autocomplete currently
{ op : "add", fieldname : "field12", value : "21:21" }, // not used, since type=time doesn't have autocomplete currently
{ op : "add", fieldname : "field13", value : "32" }, // not used, since type=range doesn't have a drop down menu
{ op : "add", fieldname : "field14", value : "#ffffff" }, // not used, since type=color doesn't have autocomplete currently
@@ -899,15 +899,13 @@ function runTest() {
input = $_(14, "field11");
restoreForm();
- expectPopup();
- doKey("down");
+ waitForMenuChange(0);
break;
case 405:
- checkMenuEntries(["2010-10-10"]);
- doKey("down");
- doKey("return");
- checkForm("2010-10-10");
+ checkMenuEntries([]); // type=date with it's own control frame does not
+ // have a drop down menu for now
+ checkForm("");
input = $_(15, "field12");
restoreForm();
diff --git a/toolkit/content/browser-content.js b/toolkit/content/browser-content.js
index 4ae798fbd..1376f70a3 100644
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -1714,6 +1714,14 @@ let DateTimePickerListener = {
(aEvent.originalTarget.type == "time" && !this.getTimePickerPref())) {
return;
}
+
+ if (this._inputElement) {
+ // This happens when we're trying to open a picker when another picker
+ // is still open. We ignore this request to let the first picker
+ // close gracefully.
+ return;
+ }
+
this._inputElement = aEvent.originalTarget;
this._inputElement.setDateTimePickerState(true);
this.addListeners();
@@ -1728,15 +1736,17 @@ let DateTimePickerListener = {
// element's value.
value: Object.keys(value).length > 0 ? value
: this._inputElement.value,
- step: this._inputElement.step,
- min: this._inputElement.min,
- max: this._inputElement.max,
+ min: this._inputElement.getMinimum(),
+ max: this._inputElement.getMaximum(),
+ step: this._inputElement.getStep(),
+ stepBase: this._inputElement.getStepBase(),
},
});
break;
}
case "MozUpdateDateTimePicker": {
let value = this._inputElement.getDateTimeInputBoxValue();
+ value.type = this._inputElement.type;
sendAsyncMessage("FormDateTime:UpdatePicker", { value });
break;
}
diff --git a/toolkit/content/customizeToolbar.js b/toolkit/content/customizeToolbar.js
index b96b60b98..7400aaadc 100644
--- a/toolkit/content/customizeToolbar.js
+++ b/toolkit/content/customizeToolbar.js
@@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+const gToolbarInfoSeparators = ["|", "-"];
+
var gToolboxDocument = null;
var gToolbox = null;
var gCurrentDragOverItem = null;
@@ -173,9 +175,20 @@ function persistCurrentSets()
// Remove custom toolbars whose contents have been removed.
gToolbox.removeChild(toolbar);
} else if (gToolbox.toolbarset) {
+ var hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
+ "autohide" : "collapsed";
// Persist custom toolbar info on the <toolbarset/>
- gToolbox.toolbarset.setAttribute("toolbar"+(++customCount),
- toolbar.toolbarName + ":" + currentSet);
+ // Attributes:
+ // Names: "toolbarX" (X - the number of the toolbar)
+ // Values: "Name|HidingAttributeName-HidingAttributeValue|CurrentSet"
+ gToolbox.toolbarset.setAttribute("toolbar" + (++customCount),
+ toolbar.toolbarName
+ + gToolbarInfoSeparators[0]
+ + hidingAttribute
+ + gToolbarInfoSeparators[1]
+ + toolbar.getAttribute(hidingAttribute)
+ + gToolbarInfoSeparators[0]
+ + currentSet);
gToolboxDocument.persist(gToolbox.toolbarset.id, "toolbar"+customCount);
}
}
@@ -485,6 +498,11 @@ function addNewToolbar()
continue;
}
+ if (name.value.includes(gToolbarInfoSeparators[0])) {
+ message = stringBundle.getFormattedString("enterToolbarIllegalChars", [name.value]);
+ continue;
+ }
+
var dupeFound = false;
// Check for an existing toolbar with the same display name
@@ -506,7 +524,7 @@ function addNewToolbar()
message = stringBundle.getFormattedString("enterToolbarDup", [name.value]);
}
- gToolbox.appendCustomToolbar(name.value, "");
+ gToolbox.appendCustomToolbar(name.value, "", [null, null]);
toolboxChanged();
diff --git a/toolkit/content/datepicker.xhtml b/toolkit/content/datepicker.xhtml
new file mode 100644
index 000000000..4da6e398f
--- /dev/null
+++ b/toolkit/content/datepicker.xhtml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+ <title>Date Picker</title>
+ <link rel="stylesheet" href="chrome://global/skin/datetimeinputpickers.css"/>
+ <script type="application/javascript" src="chrome://global/content/bindings/datekeeper.js"></script>
+ <script type="application/javascript" src="chrome://global/content/bindings/spinner.js"></script>
+ <script type="application/javascript" src="chrome://global/content/bindings/calendar.js"></script>
+ <script type="application/javascript" src="chrome://global/content/bindings/datepicker.js"></script>
+</head>
+<body>
+ <div id="date-picker">
+ <div class="calendar-container">
+ <div class="nav">
+ <button class="left"/>
+ <button class="right"/>
+ </div>
+ <div class="week-header"></div>
+ <div class="days-viewport">
+ <div class="days-view"></div>
+ </div>
+ </div>
+ <div class="month-year-container">
+ <button class="month-year"/>
+ </div>
+ <div class="month-year-view"></div>
+ </div>
+ <template id="spinner-template">
+ <div class="spinner-container">
+ <button class="up"/>
+ <div class="spinner"></div>
+ <button class="down"/>
+ </div>
+ </template>
+ <script type="application/javascript">
+ // We need to hide the scroll bar but maintain its scrolling
+ // capability, so using |overflow: hidden| is not an option.
+ // Instead, we are inserting a user agent stylesheet that is
+ // capable of selecting scrollbars, and do |display: none|.
+ var domWinUtls = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ domWinUtls.loadSheetUsingURIString('data:text/css,@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); scrollbar { display: none; }', domWinUtls.AGENT_SHEET);
+ // Create a DatePicker instance and prepare to be
+ // initialized by the "DatePickerInit" event from datetimepopup.xml
+ const root = document.getElementById("date-picker");
+ new DatePicker({
+ monthYear: root.querySelector(".month-year"),
+ monthYearView: root.querySelector(".month-year-view"),
+ buttonLeft: root.querySelector(".left"),
+ buttonRight: root.querySelector(".right"),
+ weekHeader: root.querySelector(".week-header"),
+ daysView: root.querySelector(".days-view")
+ });
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn
index 851c72250..f0d4a62a4 100644
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -45,6 +45,7 @@ toolkit.jar:
content/global/customizeToolbar.js
content/global/customizeToolbar.xul
#endif
+ content/global/datepicker.xhtml
#ifndef MOZ_FENNEC
content/global/editMenuOverlay.js
* content/global/editMenuOverlay.xul
@@ -80,8 +81,11 @@ toolkit.jar:
content/global/bindings/autocomplete.xml (widgets/autocomplete.xml)
content/global/bindings/browser.xml (widgets/browser.xml)
content/global/bindings/button.xml (widgets/button.xml)
+ content/global/bindings/calendar.js (widgets/calendar.js)
content/global/bindings/checkbox.xml (widgets/checkbox.xml)
content/global/bindings/colorpicker.xml (widgets/colorpicker.xml)
+ content/global/bindings/datekeeper.js (widgets/datekeeper.js)
+ content/global/bindings/datepicker.js (widgets/datepicker.js)
content/global/bindings/datetimepicker.xml (widgets/datetimepicker.xml)
content/global/bindings/datetimepopup.xml (widgets/datetimepopup.xml)
content/global/bindings/datetimebox.xml (widgets/datetimebox.xml)
diff --git a/toolkit/content/tests/browser/browser.ini b/toolkit/content/tests/browser/browser.ini
index 278b2ffe0..67ba2f850 100644
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -26,6 +26,7 @@ skip-if = !e10s
[browser_contentTitle.js]
[browser_crash_previous_frameloader.js]
run-if = e10s && crashreporter
+[browser_datetime_datepicker.js]
[browser_default_image_filename.js]
[browser_f7_caret_browsing.js]
[browser_findbar.js]
diff --git a/toolkit/content/tests/browser/browser_datetime_datepicker.js b/toolkit/content/tests/browser/browser_datetime_datepicker.js
new file mode 100644
index 000000000..966a74e7a
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_datetime_datepicker.js
@@ -0,0 +1,284 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const MONTH_YEAR = ".month-year",
+ DAYS_VIEW = ".days-view",
+ BTN_PREV_MONTH = ".prev",
+ BTN_NEXT_MONTH = ".next";
+const DATE_FORMAT = new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long", timeZone: "UTC" }).format;
+
+// Create a list of abbreviations for calendar class names
+const W = "weekend",
+ O = "outside",
+ S = "selection",
+ R = "out-of-range",
+ T = "today",
+ P = "off-step";
+
+// Calendar classlist for 2016-12. Used to verify the classNames are correct.
+const calendarClasslist_201612 = [
+ [W, O], [O], [O], [O], [], [], [W],
+ [W], [], [], [], [], [], [W],
+ [W], [], [], [], [S], [], [W],
+ [W], [], [], [], [], [], [W],
+ [W], [], [], [], [], [], [W],
+ [W, O], [O], [O], [O], [O], [O], [W, O],
+];
+
+function getCalendarText() {
+ return helper.getChildren(DAYS_VIEW).map(child => child.textContent);
+}
+
+function getCalendarClassList() {
+ return helper.getChildren(DAYS_VIEW).map(child => Array.from(child.classList));
+}
+
+function mergeArrays(a, b) {
+ return a.map((classlist, index) => classlist.concat(b[index]));
+}
+
+let helper = new DateTimeTestHelper();
+
+registerCleanupFunction(() => {
+ helper.cleanup();
+});
+
+/**
+ * Test that date picker opens to today's date when input field is blank
+ */
+add_task(async function test_datepicker_today() {
+ const date = new Date();
+
+ await helper.openPicker("data:text/html, <input type='date'>");
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(date));
+
+ await helper.tearDown();
+});
+
+/**
+ * Test that date picker opens to the correct month, with calendar days
+ * displayed correctly, given a date value is set.
+ */
+add_task(async function test_datepicker_open() {
+ const inputValue = "2016-12-15";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(inputValue)));
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "27", "28", "29", "30", "1", "2", "3",
+ "4", "5", "6", "7", "8", "9", "10",
+ "11", "12", "13", "14", "15", "16", "17",
+ "18", "19", "20", "21", "22", "23", "24",
+ "25", "26", "27", "28", "29", "30", "31",
+ "1", "2", "3", "4", "5", "6", "7",
+ ],
+ "2016-12",
+ );
+ Assert.deepEqual(
+ getCalendarClassList(),
+ calendarClasslist_201612,
+ "2016-12 classNames"
+ );
+
+ await helper.tearDown();
+});
+
+/**
+ * When the prev month button is clicked, calendar should display the dates for
+ * the previous month.
+ */
+add_task(async function test_datepicker_prev_month_btn() {
+ const inputValue = "2016-12-15";
+ const prevMonth = "2016-11-01";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+ helper.click(helper.getElement(BTN_PREV_MONTH));
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(prevMonth)));
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "30", "31", "1", "2", "3", "4", "5",
+ "6", "7", "8", "9", "10", "11", "12",
+ "13", "14", "15", "16", "17", "18", "19",
+ "20", "21", "22", "23", "24", "25", "26",
+ "27", "28", "29", "30", "1", "2", "3",
+ "4", "5", "6", "7", "8", "9", "10",
+ ],
+ "2016-11",
+ );
+
+ await helper.tearDown();
+});
+
+/**
+ * When the next month button is clicked, calendar should display the dates for
+ * the next month.
+ */
+add_task(async function test_datepicker_next_month_btn() {
+ const inputValue = "2016-12-15";
+ const nextMonth = "2017-01-01";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+ helper.click(helper.getElement(BTN_NEXT_MONTH));
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(nextMonth)));
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "25", "26", "27", "28", "29", "30", "31",
+ "1", "2", "3", "4", "5", "6", "7",
+ "8", "9", "10", "11", "12", "13", "14",
+ "15", "16", "17", "18", "19", "20", "21",
+ "22", "23", "24", "25", "26", "27", "28",
+ "29", "30", "31", "1", "2", "3", "4",
+ ],
+ "2017-01",
+ );
+
+ await helper.tearDown();
+});
+
+/**
+ * When a date on the calendar is clicked, date picker should close and set
+ * value to the input box.
+ */
+add_task(async function test_datepicker_clicked() {
+ const inputValue = "2016-12-15";
+ const firstDayOnCalendar = "2016-11-27";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+ // Click the first item (top-left corner) of the calendar
+ helper.click(helper.getElement(DAYS_VIEW).children[0]);
+ await ContentTask.spawn(helper.tab.linkedBrowser, {}, async function() {
+ let inputEl = content.document.querySelector("input");
+ await ContentTaskUtils.waitForEvent(inputEl, "input");
+ });
+
+ Assert.equal(content.document.querySelector("input").value, firstDayOnCalendar);
+
+ await helper.tearDown();
+});
+
+/**
+ * Make sure picker is in correct state when it is reopened.
+ */
+add_task(async function test_datepicker_reopen_state() {
+ const inputValue = "2016-12-15";
+ const nextMonth = "2017-01-01";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+ // Navigate to the next month but does not commit the change
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(inputValue)));
+ helper.click(helper.getElement(BTN_NEXT_MONTH));
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(nextMonth)));
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
+
+ // Ensures the picker opens to the month of the input value
+ await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, gBrowser.selectedBrowser);
+ await helper.waitForPickerReady();
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(inputValue)));
+
+ await helper.tearDown();
+});
+
+/**
+ * When min and max attributes are set, calendar should show some dates as
+ * out-of-range.
+ */
+add_task(async function test_datepicker_min_max() {
+ const inputValue = "2016-12-15";
+ const inputMin = "2016-12-05";
+ const inputMax = "2016-12-25";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}" min="${inputMin}" max="${inputMax}">`);
+
+ Assert.deepEqual(
+ getCalendarClassList(),
+ mergeArrays(calendarClasslist_201612, [
+ // R denotes out-of-range
+ [R], [R], [R], [R], [R], [R], [R],
+ [R], [], [], [], [], [], [],
+ [], [], [], [], [], [], [],
+ [], [], [], [], [], [], [],
+ [], [R], [R], [R], [R], [R], [R],
+ [R], [R], [R], [R], [R], [R], [R],
+ ]),
+ "2016-12 with min & max",
+ );
+
+ await helper.tearDown();
+});
+
+/**
+ * When step attribute is set, calendar should show some dates as off-step.
+ */
+add_task(async function test_datepicker_step() {
+ const inputValue = "2016-12-15";
+ const inputStep = "5";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}" step="${inputStep}">`);
+
+ Assert.deepEqual(
+ getCalendarClassList(),
+ mergeArrays(calendarClasslist_201612, [
+ // P denotes off-step
+ [P], [P], [P], [], [P], [P], [P],
+ [P], [], [P], [P], [P], [P], [],
+ [P], [P], [P], [P], [], [P], [P],
+ [P], [P], [], [P], [P], [P], [P],
+ [], [P], [P], [P], [P], [], [P],
+ [P], [P], [P], [], [P], [P], [P],
+ ]),
+ "2016-12 with step",
+ );
+
+ await helper.tearDown();
+});
+
+add_task(async function test_datepicker_abs_min() {
+ const inputValue = "0001-01-01";
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "", "1", "2", "3", "4", "5", "6",
+ "7", "8", "9", "10", "11", "12", "13",
+ "14", "15", "16", "17", "18", "19", "20",
+ "21", "22", "23", "24", "25", "26", "27",
+ "28", "29", "30", "31", "1", "2", "3",
+ "4", "5", "6", "7", "8", "9", "10",
+ ],
+ "0001-01",
+ );
+
+ await helper.tearDown();
+});
+
+add_task(async function test_datepicker_abs_max() {
+ const inputValue = "275760-09-13";
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "31", "1", "2", "3", "4", "5", "6",
+ "7", "8", "9", "10", "11", "12", "13",
+ "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "",
+ ],
+ "275760-09",
+ );
+
+ await helper.tearDown();
+});
diff --git a/toolkit/content/tests/browser/head.js b/toolkit/content/tests/browser/head.js
index 1c6c2b54f..d7ed7a9ff 100644
--- a/toolkit/content/tests/browser/head.js
+++ b/toolkit/content/tests/browser/head.js
@@ -31,3 +31,93 @@ function pushPrefs(...aPrefs) {
SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
return deferred.promise;
}
+
+/**
+ * Helper class for testing datetime input picker widget
+ */
+class DateTimeTestHelper {
+ constructor() {
+ this.panel = document.getElementById("DateTimePickerPanel");
+ this.panel.setAttribute("animate", false);
+ this.tab = null;
+ this.frame = null;
+ }
+
+ /**
+ * Opens a new tab with the URL of the test page, and make sure the picker is
+ * ready for testing.
+ *
+ * @param {String} pageUrl
+ */
+ async openPicker(pageUrl) {
+ this.tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+ await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, gBrowser.selectedBrowser);
+ // If dateTimePopupFrame doesn't exist yet, wait for the binding to be attached
+ if (!this.panel.dateTimePopupFrame) {
+ await BrowserTestUtils.waitForEvent(this.panel, "DateTimePickerBindingReady")
+ }
+ this.frame = this.panel.dateTimePopupFrame;
+ await this.waitForPickerReady();
+ }
+
+ async waitForPickerReady() {
+ await BrowserTestUtils.waitForEvent(this.frame, "load", true);
+ // Wait for picker elements to be ready
+ await BrowserTestUtils.waitForEvent(this.frame.contentDocument, "PickerReady");
+ }
+
+ /**
+ * Find an element on the picker.
+ *
+ * @param {String} selector
+ * @return {DOMElement}
+ */
+ getElement(selector) {
+ return this.frame.contentDocument.querySelector(selector);
+ }
+
+ /**
+ * Find the children of an element on the picker.
+ *
+ * @param {String} selector
+ * @return {Array<DOMElement>}
+ */
+ getChildren(selector) {
+ return Array.from(this.getElement(selector).children);
+ }
+
+ /**
+ * Click on an element
+ *
+ * @param {DOMElement} element
+ */
+ click(element) {
+ EventUtils.synthesizeMouseAtCenter(element, {}, this.frame.contentWindow);
+ }
+
+ /**
+ * Close the panel and the tab
+ */
+ async tearDown() {
+ if (!this.panel.hidden) {
+ let pickerClosePromise = new Promise(resolve => {
+ this.panel.addEventListener("popuphidden", resolve, {once: true});
+ });
+ this.panel.hidePopup();
+ this.panel.closePicker();
+ await pickerClosePromise;
+ }
+ await BrowserTestUtils.removeTab(this.tab);
+ this.tab = null;
+ }
+
+ /**
+ * Clean up after tests. Remove the frame to prevent leak.
+ */
+ cleanup() {
+ this.frame.remove();
+ this.frame = null;
+ this.panel.removeAttribute("animate");
+ this.panel = null;
+ }
+}
diff --git a/toolkit/content/timepicker.xhtml b/toolkit/content/timepicker.xhtml
index 1396223f1..77b9fba41 100644
--- a/toolkit/content/timepicker.xhtml
+++ b/toolkit/content/timepicker.xhtml
@@ -6,7 +6,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<head>
<title>Time Picker</title>
- <link rel="stylesheet" href="chrome://global/skin/timepicker.css"/>
+ <link rel="stylesheet" href="chrome://global/skin/datetimeinputpickers.css"/>
<script type="application/javascript" src="chrome://global/content/bindings/timekeeper.js"></script>
<script type="application/javascript" src="chrome://global/content/bindings/spinner.js"></script>
<script type="application/javascript" src="chrome://global/content/bindings/timepicker.js"></script>
diff --git a/toolkit/content/widgets/calendar.js b/toolkit/content/widgets/calendar.js
new file mode 100644
index 000000000..44ba67501
--- /dev/null
+++ b/toolkit/content/widgets/calendar.js
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Initialize the Calendar and generate nodes for week headers and days, and
+ * attach event listeners.
+ *
+ * @param {Object} options
+ * {
+ * {Number} calViewSize: Number of days to appear on a calendar view
+ * {Function} getDayString: Transform day number to string
+ * {Function} getWeekHeaderString: Transform day of week number to string
+ * {Function} setSelection: Set selection for dateKeeper
+ * }
+ * @param {Object} context
+ * {
+ * {DOMElement} weekHeader
+ * {DOMElement} daysView
+ * }
+ */
+function Calendar(options, context) {
+ const DAYS_IN_A_WEEK = 7;
+
+ this.context = context;
+ this.state = {
+ days: [],
+ weekHeaders: [],
+ setSelection: options.setSelection,
+ getDayString: options.getDayString,
+ getWeekHeaderString: options.getWeekHeaderString
+ };
+ this.elements = {
+ weekHeaders: this._generateNodes(DAYS_IN_A_WEEK, context.weekHeader),
+ daysView: this._generateNodes(options.calViewSize, context.daysView)
+ };
+
+ this._attachEventListeners();
+}
+
+{
+ Calendar.prototype = {
+
+ /**
+ * Set new properties and render them.
+ *
+ * @param {Object} props
+ * {
+ * {Boolean} isVisible: Whether or not the calendar is in view
+ * {Array<Object>} days: Data for days
+ * {
+ * {Date} dateObj
+ * {Number} content
+ * {Array<String>} classNames
+ * {Boolean} enabled
+ * }
+ * {Array<Object>} weekHeaders: Data for weekHeaders
+ * {
+ * {Number} content
+ * {Array<String>} classNames
+ * }
+ * }
+ */
+ setProps(props) {
+ if (props.isVisible) {
+ // Transform the days and weekHeaders array for rendering
+ const days = props.days.map(({ dateObj, content, classNames, enabled }) => {
+ return {
+ dateObj,
+ textContent: this.state.getDayString(content),
+ className: classNames.join(" "),
+ enabled
+ };
+ });
+ const weekHeaders = props.weekHeaders.map(({ content, classNames }) => {
+ return {
+ textContent: this.state.getWeekHeaderString(content),
+ className: classNames.join(" ")
+ };
+ });
+ // Update the DOM nodes states
+ this._render({
+ elements: this.elements.daysView,
+ items: days,
+ prevState: this.state.days
+ });
+ this._render({
+ elements: this.elements.weekHeaders,
+ items: weekHeaders,
+ prevState: this.state.weekHeaders,
+ });
+ // Update the state to current
+ this.state.days = days;
+ this.state.weekHeaders = weekHeaders;
+ }
+ },
+
+ /**
+ * Render the items onto the DOM nodes
+ * @param {Object}
+ * {
+ * {Array<DOMElement>} elements
+ * {Array<Object>} items
+ * {Array<Object>} prevState: state of items from last render
+ * }
+ */
+ _render({ elements, items, prevState }) {
+ for (let i = 0, l = items.length; i < l; i++) {
+ let el = elements[i];
+
+ // Check if state from last render has changed, if so, update the elements
+ if (!prevState[i] || prevState[i].textContent != items[i].textContent) {
+ el.textContent = items[i].textContent;
+ }
+ if (!prevState[i] || prevState[i].className != items[i].className) {
+ el.className = items[i].className;
+ }
+ }
+ },
+
+ /**
+ * Generate DOM nodes
+ *
+ * @param {Number} size: Number of nodes to generate
+ * @param {DOMElement} context: Element to append the nodes to
+ * @return {Array<DOMElement>}
+ */
+ _generateNodes(size, context) {
+ let frag = document.createDocumentFragment();
+ let refs = [];
+
+ for (let i = 0; i < size; i++) {
+ let el = document.createElement("div");
+ el.dataset.id = i;
+ refs.push(el);
+ frag.appendChild(el);
+ }
+ context.appendChild(frag);
+
+ return refs;
+ },
+
+ /**
+ * Handle events
+ * @param {DOMEvent} event
+ */
+ handleEvent(event) {
+ switch (event.type) {
+ case "click": {
+ if (event.target.parentNode == this.context.daysView) {
+ let targetId = event.target.dataset.id;
+ let targetObj = this.state.days[targetId];
+ if (targetObj.enabled) {
+ this.state.setSelection(targetObj.dateObj);
+ }
+ }
+ break;
+ }
+ }
+ },
+
+ /**
+ * Attach event listener to daysView
+ */
+ _attachEventListeners() {
+ this.context.daysView.addEventListener("click", this);
+ }
+ };
+}
diff --git a/toolkit/content/widgets/datekeeper.js b/toolkit/content/widgets/datekeeper.js
new file mode 100644
index 000000000..5d70416a9
--- /dev/null
+++ b/toolkit/content/widgets/datekeeper.js
@@ -0,0 +1,336 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * DateKeeper keeps track of the date states.
+ */
+function DateKeeper(props) {
+ this.init(props);
+}
+
+{
+ const DAYS_IN_A_WEEK = 7,
+ MONTHS_IN_A_YEAR = 12,
+ YEAR_VIEW_SIZE = 200,
+ YEAR_BUFFER_SIZE = 10,
+ // The min value is 0001-01-01 based on HTML spec:
+ // https://html.spec.whatwg.org/#valid-date-string
+ MIN_DATE = -62135596800000,
+ // The max value is derived from the ECMAScript spec (275760-09-13):
+ // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
+ MAX_DATE = 8640000000000000,
+ MAX_YEAR = 275760,
+ MAX_MONTH = 9;
+
+ DateKeeper.prototype = {
+ get year() {
+ return this.state.dateObj.getUTCFullYear();
+ },
+
+ get month() {
+ return this.state.dateObj.getUTCMonth();
+ },
+
+ get selection() {
+ return this.state.selection;
+ },
+
+ /**
+ * Initialize DateKeeper
+ * @param {Number} year
+ * @param {Number} month
+ * @param {Number} day
+ * @param {Number} min
+ * @param {Number} max
+ * @param {Number} step
+ * @param {Number} stepBase
+ * @param {Number} firstDayOfWeek
+ * @param {Array<Number>} weekends
+ * @param {Number} calViewSize
+ */
+ init({ year, month, day, min, max, step, stepBase, firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) {
+ const today = new Date();
+
+ this.state = {
+ step, firstDayOfWeek, weekends, calViewSize,
+ // min & max are NaN if empty or invalid
+ min: new Date(Number.isNaN(min) ? MIN_DATE : min),
+ max: new Date(Number.isNaN(max) ? MAX_DATE : max),
+ stepBase: new Date(stepBase),
+ today: this._newUTCDate(today.getFullYear(), today.getMonth(), today.getDate()),
+ weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends),
+ years: [],
+ dateObj: new Date(0),
+ selection: { year, month, day },
+ };
+
+ this.setCalendarMonth({
+ year: year === undefined ? today.getFullYear() : year,
+ month: month === undefined ? today.getMonth() : month
+ });
+ },
+ /**
+ * Set new calendar month. The year is always treated as full year, so the
+ * short-form is not supported.
+ * @param {Object} date parts
+ * {
+ * {Number} year [optional]
+ * {Number} month [optional]
+ * }
+ */
+ setCalendarMonth({ year = this.year, month = this.month }) {
+ // Make sure the date is valid before setting.
+ // Use setUTCFullYear so that year 99 doesn't get parsed as 1999
+ if (year > MAX_YEAR || year === MAX_YEAR && month >= MAX_MONTH) {
+ this.state.dateObj.setUTCFullYear(MAX_YEAR, MAX_MONTH - 1, 1);
+ } else if (year < 1 || year === 1 && month < 0) {
+ this.state.dateObj.setUTCFullYear(1, 0, 1);
+ } else {
+ this.state.dateObj.setUTCFullYear(year, month, 1);
+ }
+ },
+
+ /**
+ * Set selection date
+ * @param {Number} year
+ * @param {Number} month
+ * @param {Number} day
+ */
+ setSelection({ year, month, day }) {
+ this.state.selection.year = year;
+ this.state.selection.month = month;
+ this.state.selection.day = day;
+ },
+
+ /**
+ * Set month. Makes sure the day is <= the last day of the month
+ * @param {Number} month
+ */
+ setMonth(month) {
+ this.setCalendarMonth({ year: this.year, month });
+ },
+
+ /**
+ * Set year. Makes sure the day is <= the last day of the month
+ * @param {Number} year
+ */
+ setYear(year) {
+ this.setCalendarMonth({ year, month: this.month });
+ },
+
+ /**
+ * Set month by offset. Makes sure the day is <= the last day of the month
+ * @param {Number} offset
+ */
+ setMonthByOffset(offset) {
+ this.setCalendarMonth({ year: this.year, month: this.month + offset });
+ },
+
+ /**
+ * Generate the array of months
+ * @return {Array<Object>}
+ * {
+ * {Number} value: Month in int
+ * {Boolean} enabled
+ * }
+ */
+ getMonths() {
+ let months = [];
+
+ for (let i = 0; i < MONTHS_IN_A_YEAR; i++) {
+ months.push({
+ value: i,
+ enabled: true
+ });
+ }
+
+ return months;
+ },
+
+ /**
+ * Generate the array of years
+ * @return {Array<Object>}
+ * {
+ * {Number} value: Year in int
+ * {Boolean} enabled
+ * }
+ */
+ getYears() {
+ let years = [];
+
+ const firstItem = this.state.years[0];
+ const lastItem = this.state.years[this.state.years.length - 1];
+ const currentYear = this.year;
+
+ // Generate new years array when the year is outside of the first &
+ // last item range. If not, return the cached result.
+ if (!firstItem || !lastItem ||
+ currentYear <= firstItem.value + YEAR_BUFFER_SIZE ||
+ currentYear >= lastItem.value - YEAR_BUFFER_SIZE) {
+ // The year is set in the middle with items on both directions
+ for (let i = -(YEAR_VIEW_SIZE / 2); i < YEAR_VIEW_SIZE / 2; i++) {
+ const year = currentYear + i;
+ if (year >= 1 && year <= MAX_YEAR) {
+ years.push({
+ value: year,
+ enabled: true
+ });
+ }
+ }
+ this.state.years = years;
+ }
+ return this.state.years;
+ },
+
+ /**
+ * Get days for calendar
+ * @return {Array<Object>}
+ * {
+ * {Date} dateObj
+ * {Number} content
+ * {Array<String>} classNames
+ * {Boolean} enabled
+ * }
+ */
+ getDays() {
+ const firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek);
+ const month = this.month;
+ let days = [];
+
+ for (let i = 0; i < this.state.calViewSize; i++) {
+ const dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(),
+ firstDayOfMonth.getUTCMonth(),
+ firstDayOfMonth.getUTCDate() + i);
+
+ let classNames = [];
+ let enabled = true;
+
+ const isValid = dateObj.getTime() >= MIN_DATE && dateObj.getTime() <= MAX_DATE;
+ if (!isValid) {
+ classNames.push("out-of-range");
+ enabled = false;
+
+ days.push({
+ classNames,
+ enabled,
+ });
+ continue;
+ }
+
+ const isWeekend = this.state.weekends.includes(dateObj.getUTCDay());
+ const isCurrentMonth = month == dateObj.getUTCMonth();
+ const isSelection = this.state.selection.year == dateObj.getUTCFullYear() &&
+ this.state.selection.month == dateObj.getUTCMonth() &&
+ this.state.selection.day == dateObj.getUTCDate();
+ const isOutOfRange = dateObj.getTime() < this.state.min.getTime() ||
+ dateObj.getTime() > this.state.max.getTime();
+ const isToday = this.state.today.getTime() == dateObj.getTime();
+ const isOffStep = this._checkIsOffStep(dateObj,
+ this._newUTCDate(dateObj.getUTCFullYear(),
+ dateObj.getUTCMonth(),
+ dateObj.getUTCDate() + 1));
+
+ if (isWeekend) {
+ classNames.push("weekend");
+ }
+ if (!isCurrentMonth) {
+ classNames.push("outside");
+ }
+ if (isSelection && !isOutOfRange && !isOffStep) {
+ classNames.push("selection");
+ }
+ if (isOutOfRange) {
+ classNames.push("out-of-range");
+ enabled = false;
+ }
+ if (isToday) {
+ classNames.push("today");
+ }
+ if (isOffStep) {
+ classNames.push("off-step");
+ enabled = false;
+ }
+ days.push({
+ dateObj,
+ content: dateObj.getUTCDate(),
+ classNames,
+ enabled,
+ });
+ }
+ return days;
+ },
+
+ /**
+ * Check if a date is off step given a starting point and the next increment
+ * @param {Date} start
+ * @param {Date} next
+ * @return {Boolean}
+ */
+ _checkIsOffStep(start, next) {
+ // If the increment is larger or equal to the step, it must not be off-step.
+ if (next - start >= this.state.step) {
+ return false;
+ }
+ // Calculate the last valid date
+ const lastValidStep = Math.floor((next - 1 - this.state.stepBase) / this.state.step);
+ const lastValidTimeInMs = lastValidStep * this.state.step + this.state.stepBase.getTime();
+ // The date is off-step if the last valid date is smaller than the start date
+ return lastValidTimeInMs < start.getTime();
+ },
+
+ /**
+ * Get week headers for calendar
+ * @param {Number} firstDayOfWeek
+ * @param {Array<Number>} weekends
+ * @return {Array<Object>}
+ * {
+ * {Number} content
+ * {Array<String>} classNames
+ * }
+ */
+ _getWeekHeaders(firstDayOfWeek, weekends) {
+ let headers = [];
+ let dayOfWeek = firstDayOfWeek;
+
+ for (let i = 0; i < DAYS_IN_A_WEEK; i++) {
+ headers.push({
+ content: dayOfWeek % DAYS_IN_A_WEEK,
+ classNames: weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : []
+ });
+ dayOfWeek++;
+ }
+ return headers;
+ },
+
+ /**
+ * Get the first day on a calendar month
+ * @param {Date} dateObj
+ * @param {Number} firstDayOfWeek
+ * @return {Date}
+ */
+ _getFirstCalendarDate(dateObj, firstDayOfWeek) {
+ const daysOffset = 1 - DAYS_IN_A_WEEK;
+ let firstDayOfMonth = this._newUTCDate(dateObj.getUTCFullYear(), dateObj.getUTCMonth());
+ let dayOfWeek = firstDayOfMonth.getUTCDay();
+
+ return this._newUTCDate(
+ firstDayOfMonth.getUTCFullYear(),
+ firstDayOfMonth.getUTCMonth(),
+ // When first calendar date is the same as first day of the week, add
+ // another row on top of it.
+ firstDayOfWeek == dayOfWeek ? daysOffset : (firstDayOfWeek - dayOfWeek + daysOffset) % DAYS_IN_A_WEEK);
+ },
+
+ /**
+ * Helper function for creating UTC dates
+ * @param {...[Number]} parts
+ * @return {Date}
+ */
+ _newUTCDate(...parts) {
+ return new Date(new Date(0).setUTCFullYear(...parts));
+ },
+ };
+}
diff --git a/toolkit/content/widgets/datepicker.js b/toolkit/content/widgets/datepicker.js
new file mode 100644
index 000000000..0e9c9a6e6
--- /dev/null
+++ b/toolkit/content/widgets/datepicker.js
@@ -0,0 +1,376 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function DatePicker(context) {
+ this.context = context;
+ this._attachEventListeners();
+}
+
+{
+ const CAL_VIEW_SIZE = 42;
+
+ DatePicker.prototype = {
+ /**
+ * Initializes the date picker. Set the default states and properties.
+ * @param {Object} props
+ * {
+ * {Number} year [optional]
+ * {Number} month [optional]
+ * {Number} date [optional]
+ * {Number} min
+ * {Number} max
+ * {Number} step
+ * {Number} stepBase
+ * {Number} firstDayOfWeek
+ * {Array<Number>} weekends
+ * {Array<String>} monthStrings
+ * {Array<String>} weekdayStrings
+ * {String} locale [optional]: User preferred locale
+ * }
+ */
+ init(props = {}) {
+ this.props = props;
+ this._setDefaultState();
+ this._createComponents();
+ this._update();
+ document.dispatchEvent(new CustomEvent("PickerReady"));
+ },
+
+ /*
+ * Set initial date picker states.
+ */
+ _setDefaultState() {
+ const { year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends,
+ monthStrings, weekdayStrings, locale } = this.props;
+ const dateKeeper = new DateKeeper({
+ year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends,
+ calViewSize: CAL_VIEW_SIZE
+ });
+
+ this.state = {
+ dateKeeper,
+ locale,
+ isMonthPickerVisible: false,
+ getDayString: day => day ? new Intl.NumberFormat(locale).format(day) : "",
+ getWeekHeaderString: weekday => weekdayStrings[weekday],
+ getMonthString: month => monthStrings[month],
+ setSelection: date => {
+ dateKeeper.setSelection({
+ year: date.getUTCFullYear(),
+ month: date.getUTCMonth(),
+ day: date.getUTCDate(),
+ });
+ this._update();
+ this._dispatchState();
+ this._closePopup();
+ },
+ setYear: year => {
+ dateKeeper.setYear(year);
+ dateKeeper.setSelection({
+ year,
+ month: dateKeeper.selection.month,
+ day: dateKeeper.selection.day,
+ });
+ this._update();
+ this._dispatchState();
+ },
+ setMonth: month => {
+ dateKeeper.setMonth(month);
+ dateKeeper.setSelection({
+ year: dateKeeper.selection.year,
+ month,
+ day: dateKeeper.selection.day,
+ });
+ this._update();
+ this._dispatchState();
+ },
+ toggleMonthPicker: () => {
+ this.state.isMonthPickerVisible = !this.state.isMonthPickerVisible;
+ this._update();
+ }
+ };
+ },
+
+ /**
+ * Initalize the date picker components.
+ */
+ _createComponents() {
+ this.components = {
+ calendar: new Calendar({
+ calViewSize: CAL_VIEW_SIZE,
+ locale: this.state.locale,
+ setSelection: this.state.setSelection,
+ getDayString: this.state.getDayString,
+ getWeekHeaderString: this.state.getWeekHeaderString
+ }, {
+ weekHeader: this.context.weekHeader,
+ daysView: this.context.daysView
+ }),
+ monthYear: new MonthYear({
+ setYear: this.state.setYear,
+ setMonth: this.state.setMonth,
+ getMonthString: this.state.getMonthString,
+ locale: this.state.locale
+ }, {
+ monthYear: this.context.monthYear,
+ monthYearView: this.context.monthYearView
+ })
+ };
+ },
+
+ /**
+ * Update date picker and its components.
+ */
+ _update(options = {}) {
+ const { dateKeeper, isMonthPickerVisible } = this.state;
+
+ if (isMonthPickerVisible) {
+ this.state.months = dateKeeper.getMonths();
+ this.state.years = dateKeeper.getYears();
+ } else {
+ this.state.days = dateKeeper.getDays();
+ }
+
+ this.components.monthYear.setProps({
+ isVisible: isMonthPickerVisible,
+ dateObj: dateKeeper.state.dateObj,
+ months: this.state.months,
+ years: this.state.years,
+ toggleMonthPicker: this.state.toggleMonthPicker,
+ noSmoothScroll: options.noSmoothScroll
+ });
+ this.components.calendar.setProps({
+ isVisible: !isMonthPickerVisible,
+ days: this.state.days,
+ weekHeaders: dateKeeper.state.weekHeaders
+ });
+
+ isMonthPickerVisible ?
+ this.context.monthYearView.classList.remove("hidden") :
+ this.context.monthYearView.classList.add("hidden");
+ },
+
+ /**
+ * Use postMessage to close the picker.
+ */
+ _closePopup() {
+ window.postMessage({
+ name: "ClosePopup"
+ }, "*");
+ },
+
+ /**
+ * Use postMessage to pass the state of picker to the panel.
+ */
+ _dispatchState() {
+ const { year, month, day } = this.state.dateKeeper.selection;
+ // The panel is listening to window for postMessage event, so we
+ // do postMessage to itself to send data to input boxes.
+ window.postMessage({
+ name: "PickerPopupChanged",
+ detail: {
+ year,
+ month,
+ day,
+ }
+ }, "*");
+ },
+
+ /**
+ * Attach event listeners
+ */
+ _attachEventListeners() {
+ window.addEventListener("message", this);
+ document.addEventListener("mouseup", this, { passive: true });
+ document.addEventListener("mousedown", this);
+ },
+
+ /**
+ * Handle events.
+ *
+ * @param {Event} event
+ */
+ handleEvent(event) {
+ switch (event.type) {
+ case "message": {
+ this.handleMessage(event);
+ break;
+ }
+ case "mousedown": {
+ // Use preventDefault to keep focus on input boxes
+ event.preventDefault();
+ event.target.setCapture();
+
+ if (event.target == this.context.buttonLeft) {
+ event.target.classList.add("active");
+ this.state.dateKeeper.setMonthByOffset(-1);
+ this._update();
+ } else if (event.target == this.context.buttonRight) {
+ event.target.classList.add("active");
+ this.state.dateKeeper.setMonthByOffset(1);
+ this._update();
+ }
+ break;
+ }
+ case "mouseup": {
+ if (event.target == this.context.buttonLeft || event.target == this.context.buttonRight) {
+ event.target.classList.remove("active");
+ }
+
+ }
+ }
+ },
+
+ /**
+ * Handle postMessage events.
+ *
+ * @param {Event} event
+ */
+ handleMessage(event) {
+ switch (event.data.name) {
+ case "PickerSetValue": {
+ this.set(event.data.detail);
+ break;
+ }
+ case "PickerInit": {
+ this.init(event.data.detail);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Set the date state and update the components with the new state.
+ *
+ * @param {Object} dateState
+ * {
+ * {Number} year [optional]
+ * {Number} month [optional]
+ * {Number} date [optional]
+ * }
+ */
+ set({ year, month, day }) {
+ const { dateKeeper } = this.state;
+
+ dateKeeper.setCalendarMonth({
+ year, month
+ });
+ dateKeeper.setSelection({
+ year, month, day
+ });
+ this._update({ noSmoothScroll: true });
+ }
+ };
+
+ /**
+ * MonthYear is a component that handles the month & year spinners
+ *
+ * @param {Object} options
+ * {
+ * {String} locale
+ * {Function} setYear
+ * {Function} setMonth
+ * {Function} getMonthString
+ * }
+ * @param {DOMElement} context
+ */
+ function MonthYear(options, context) {
+ const spinnerSize = 5;
+ const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
+ timeZone: "UTC" }).format;
+ const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
+ month: "long",
+ timeZone: "UTC" }).format;
+ this.context = context;
+ this.state = { dateFormat };
+ this.props = {};
+ this.components = {
+ month: new Spinner({
+ setValue: month => {
+ this.state.isMonthSet = true;
+ options.setMonth(month);
+ },
+ getDisplayString: options.getMonthString,
+ viewportSize: spinnerSize
+ }, context.monthYearView),
+ year: new Spinner({
+ setValue: year => {
+ this.state.isYearSet = true;
+ options.setYear(year);
+ },
+ getDisplayString: year => yearFormat(new Date(new Date(0).setUTCFullYear(year))),
+ viewportSize: spinnerSize
+ }, context.monthYearView)
+ };
+
+ this._attachEventListeners();
+ }
+
+ MonthYear.prototype = {
+
+ /**
+ * Set new properties and pass them to components
+ *
+ * @param {Object} props
+ * {
+ * {Boolean} isVisible
+ * {Date} dateObj
+ * {Array<Object>} months
+ * {Array<Object>} years
+ * {Function} toggleMonthPicker
+ * }
+ */
+ setProps(props) {
+ this.context.monthYear.textContent = this.state.dateFormat(props.dateObj);
+
+ if (props.isVisible) {
+ this.context.monthYear.classList.add("active");
+ this.components.month.setState({
+ value: props.dateObj.getUTCMonth(),
+ items: props.months,
+ isInfiniteScroll: true,
+ isValueSet: this.state.isMonthSet,
+ smoothScroll: !(this.state.firstOpened || props.noSmoothScroll)
+ });
+ this.components.year.setState({
+ value: props.dateObj.getUTCFullYear(),
+ items: props.years,
+ isInfiniteScroll: false,
+ isValueSet: this.state.isYearSet,
+ smoothScroll: !(this.state.firstOpened || props.noSmoothScroll)
+ });
+ this.state.firstOpened = false;
+ } else {
+ this.context.monthYear.classList.remove("active");
+ this.state.isMonthSet = false;
+ this.state.isYearSet = false;
+ this.state.firstOpened = true;
+ }
+
+ this.props = Object.assign(this.props, props);
+ },
+
+ /**
+ * Handle events
+ * @param {DOMEvent} event
+ */
+ handleEvent(event) {
+ switch (event.type) {
+ case "click": {
+ this.props.toggleMonthPicker();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Attach event listener to monthYear button
+ */
+ _attachEventListeners() {
+ this.context.monthYear.addEventListener("click", this);
+ }
+ };
+}
diff --git a/toolkit/content/widgets/datetimebox.css b/toolkit/content/widgets/datetimebox.css
index 4a9593a69..ce638078f 100644
--- a/toolkit/content/widgets/datetimebox.css
+++ b/toolkit/content/widgets/datetimebox.css
@@ -8,9 +8,17 @@
.datetime-input-box-wrapper {
-moz-appearance: none;
display: inline-flex;
+ flex: 1;
cursor: default;
background-color: inherit;
color: inherit;
+ min-width: 0;
+ justify-content: space-between;
+}
+
+.datetime-input-edit-wrapper {
+ overflow: hidden;
+ white-space: nowrap;
}
.datetime-input {
@@ -20,6 +28,8 @@
border: 0;
margin: 0;
ime-mode: disabled;
+ cursor: default;
+ -moz-user-select: none;
}
.datetime-separator {
@@ -41,5 +51,5 @@
height: 12px;
width: 12px;
align-self: center;
- justify-content: flex-end;
+ flex: none;
}
diff --git a/toolkit/content/widgets/datetimebox.xml b/toolkit/content/widgets/datetimebox.xml
index 05591e65a..94574038a 100644
--- a/toolkit/content/widgets/datetimebox.xml
+++ b/toolkit/content/widgets/datetimebox.xml
@@ -4,12 +4,466 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE bindings [
+<!ENTITY % datetimeboxDTD SYSTEM "chrome://global/locale/datetimebox.dtd">
+%datetimeboxDTD;
+]>
+
+<!--
+TODO
+Bug 1446342:
+Input type="date" not working if the other form elements has name="document"
+
+Any alternative solution:
+document === window.document
+document === this.ownerDocument
+-->
+
<bindings id="datetimeboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
+ <binding id="date-input"
+ extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base">
+ <resources>
+ <stylesheet src="chrome://global/content/textbox.css"/>
+ <stylesheet src="chrome://global/skin/textbox.css"/>
+ <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
+ </resources>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ /* eslint-disable no-multi-spaces */
+ this.mYearPlaceHolder = ]]>"&date.year.placeholder;"<![CDATA[;
+ this.mMonthPlaceHolder = ]]>"&date.month.placeholder;"<![CDATA[;
+ this.mDayPlaceHolder = ]]>"&date.day.placeholder;"<![CDATA[;
+ this.mSeparatorText = "/";
+ /* eslint-enable no-multi-spaces */
+
+ this.mMinMonth = 1;
+ this.mMaxMonth = 12;
+ this.mMinDay = 1;
+ this.mMaxDay = 31;
+ this.mMinYear = 1;
+ // Maximum year limited by ECMAScript date object range, year <= 275760.
+ this.mMaxYear = 275760;
+ this.mMonthDayLength = 2;
+ this.mYearLength = 4;
+ this.mMonthPageUpDownInterval = 3;
+ this.mDayPageUpDownInterval = 7;
+ this.mYearPageUpDownInterval = 10;
+
+ // Default to en-US, month-day-year order.
+ this.mMonthField =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-one");
+ this.mDayField =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-two");
+ this.mYearField =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-three");
+ this.mYearField.size = this.mYearLength;
+ this.mYearField.maxLength = this.mMaxYear.toString().length;
+
+ this.mMonthField.placeholder = this.mMonthPlaceHolder;
+ this.mDayField.placeholder = this.mDayPlaceHolder;
+ this.mYearField.placeholder = this.mYearPlaceHolder;
+
+ this.mMonthField.setAttribute("min", this.mMinMonth);
+ this.mMonthField.setAttribute("max", this.mMaxMonth);
+ this.mMonthField.setAttribute("pginterval",
+ this.mMonthPageUpDownInterval);
+ this.mDayField.setAttribute("min", this.mMinDay);
+ this.mDayField.setAttribute("max", this.mMaxDay);
+ this.mDayField.setAttribute("pginterval", this.mDayPageUpDownInterval);
+ this.mYearField.setAttribute("min", this.mMinYear);
+ this.mYearField.setAttribute("max", this.mMaxYear);
+ this.mYearField.setAttribute("pginterval",
+ this.mYearPageUpDownInterval);
+
+ this.mDaySeparator =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
+ this.mDaySeparator.textContent = this.mSeparatorText;
+ this.mYearSeparator =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
+ this.mYearSeparator.textContent = this.mSeparatorText;
+
+ if (this.mInputElement.value) {
+ this.setFieldsFromInputValue();
+ }
+ this.updateResetButtonVisibility();
+ ]]>
+ </constructor>
+
+ <method name="clearInputFields">
+ <parameter name="aFromInputElement"/>
+ <body>
+ <![CDATA[
+ this.log("clearInputFields");
+
+ if (this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ if (this.mMonthField && !this.mMonthField.disabled &&
+ !this.mMonthField.readOnly) {
+ this.mMonthField.value = "";
+ this.mMonthField.setAttribute("typeBuffer", "");
+ }
+
+ if (this.mDayField && !this.mDayField.disabled &&
+ !this.mDayField.readOnly) {
+ this.mDayField.value = "";
+ this.mDayField.setAttribute("typeBuffer", "");
+ }
+
+ if (this.mYearField && !this.mYearField.disabled &&
+ !this.mYearField.readOnly) {
+ this.mYearField.value = "";
+ this.mYearField.setAttribute("typeBuffer", "");
+ }
+
+ if (!aFromInputElement && this.mInputElement.value) {
+ this.mInputElement.setUserInput("");
+ }
+
+ this.updateResetButtonVisibility();
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldsFromInputValue">
+ <body>
+ <![CDATA[
+ let value = this.mInputElement.value;
+ if (!value) {
+ this.clearInputFields(true);
+ return;
+ }
+
+ this.log("setFieldsFromInputValue: " + value);
+ let [year, month, day] = value.split("-");
+
+ this.setFieldValue(this.mYearField, year);
+ this.setFieldValue(this.mMonthField, month);
+ this.setFieldValue(this.mDayField, day);
+
+ this.notifyPicker();
+ ]]>
+ </body>
+ </method>
+
+ <method name="getDaysInMonth">
+ <parameter name="aMonth"/>
+ <parameter name="aYear"/>
+ <body>
+ <![CDATA[
+ // Javascript's month is 0-based, so this means last day of the
+ // previous month.
+ return new Date(aYear, aMonth, 0).getDate();
+ ]]>
+ </body>
+ </method>
+
+ <method name="isFieldInvalid">
+ <parameter name="aField"/>
+ <body>
+ <![CDATA[
+ if (this.isEmpty(aField.value)) {
+ return true;
+ }
+
+ let min = Number(aField.getAttribute("min"));
+ let max = Number(aField.getAttribute("max"));
+
+ if (Number(aField.value) < min || Number(aField.value) > max) {
+ return true;
+ }
+
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="setInputValueFromFields">
+ <body>
+ <![CDATA[
+ if (!this.isAnyValueAvailable(false) && this.mInputElement.value) {
+ // Values in the input box was cleared, clear the input element's
+ // value if not empty.
+ this.mInputElement.setUserInput("");
+ return;
+ }
+
+ if (this.isFieldInvalid(this.mYearField) ||
+ this.isFieldInvalid(this.mMonthField) ||
+ this.isFieldInvalid(this.mDayField)) {
+ // We still need to notify picker in case any of the field has
+ // changed. If we can set input element value, then notifyPicker
+ // will be called in setFieldsFromInputValue().
+ this.notifyPicker();
+ return;
+ }
+
+ let year = this.mYearField.value;
+ let month = this.mMonthField.value;
+ let day = this.mDayField.value;
+
+ if (day > this.getDaysInMonth(month, year)) {
+ // Don't set invalid date, otherwise input element's value will be
+ // set to empty.
+ return;
+ }
+
+ let date = [year, month, day].join("-");
+
+ if (date == this.mInputElement.value) {
+ return;
+ }
+
+ this.log("setInputValueFromFields: " + date);
+ this.mInputElement.setUserInput(date);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldsFromPicker">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ let year = aValue.year;
+ let month = aValue.month;
+ let day = aValue.day;
+
+ if (!this.isEmpty(year)) {
+ this.setFieldValue(this.mYearField, year);
+ }
+
+ if (!this.isEmpty(month)) {
+ this.setFieldValue(this.mMonthField, month);
+ }
+
+ if (!this.isEmpty(day)) {
+ this.setFieldValue(this.mDayField, day);
+ }
+
+ // Update input element's .value if needed.
+ this.setInputValueFromFields();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleKeypress">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ let targetField = aEvent.originalTarget;
+ let key = aEvent.key;
+
+ if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) {
+ let buffer = targetField.getAttribute("typeBuffer") || "";
+
+ buffer = buffer.concat(key);
+ this.setFieldValue(targetField, buffer);
+ targetField.select();
+
+ let n = Number(buffer);
+ let max = targetField.getAttribute("max");
+ if (buffer.length >= targetField.maxLength || n * 10 > max) {
+ buffer = "";
+ this.advanceToNextField();
+ }
+ targetField.setAttribute("typeBuffer", buffer);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="incrementFieldValue">
+ <parameter name="aTargetField"/>
+ <parameter name="aTimes"/>
+ <body>
+ <![CDATA[
+ let value;
+
+ // Use current date if field is empty.
+ if (this.isEmpty(aTargetField.value)) {
+ let now = new Date();
+
+ if (aTargetField == this.mYearField) {
+ value = now.getFullYear();
+ } else if (aTargetField == this.mMonthField) {
+ value = now.getMonth() + 1;
+ } else if (aTargetField == this.mDayField) {
+ value = now.getDate();
+ } else {
+ this.log("Field not supported in incrementFieldValue.");
+ return;
+ }
+ } else {
+ value = Number(aTargetField.value);
+ }
+
+ let min = Number(aTargetField.getAttribute("min"));
+ let max = Number(aTargetField.getAttribute("max"));
+
+ value += Number(aTimes);
+ if (value > max) {
+ value -= (max - min + 1);
+ } else if (value < min) {
+ value += (max - min + 1);
+ }
+ this.setFieldValue(aTargetField, value);
+ aTargetField.select();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleKeyboardNav">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ let targetField = aEvent.originalTarget;
+ let key = aEvent.key;
+
+ // Home/End key does nothing on year field.
+ if (targetField == this.mYearField && (key == "Home" ||
+ key == "End")) {
+ return;
+ }
+
+ switch (key) {
+ case "ArrowUp":
+ this.incrementFieldValue(targetField, 1);
+ break;
+ case "ArrowDown":
+ this.incrementFieldValue(targetField, -1);
+ break;
+ case "PageUp": {
+ let interval = targetField.getAttribute("pginterval");
+ this.incrementFieldValue(targetField, interval);
+ break;
+ }
+ case "PageDown": {
+ let interval = targetField.getAttribute("pginterval");
+ this.incrementFieldValue(targetField, 0 - interval);
+ break;
+ }
+ case "Home":
+ let min = targetField.getAttribute("min");
+ this.setFieldValue(targetField, min);
+ targetField.select();
+ break;
+ case "End":
+ let max = targetField.getAttribute("max");
+ this.setFieldValue(targetField, max);
+ targetField.select();
+ break;
+ }
+ this.setInputValueFromFields();
+ ]]>
+ </body>
+ </method>
+
+ <method name="getCurrentValue">
+ <body>
+ <![CDATA[
+ let year;
+ if (!this.isEmpty(this.mYearField.value)) {
+ year = Number(this.mYearField.value);
+ }
+
+ let month;
+ if (!this.isEmpty(this.mMonthField.value)) {
+ month = Number(this.mMonthField.value);
+ }
+
+ let day;
+ if (!this.isEmpty(this.mDayField.value)) {
+ day = Number(this.mDayField.value);
+ }
+
+ let date = { year, month, day };
+
+ this.log("getCurrentValue: " + JSON.stringify(date));
+ return date;
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ let value = Number(aValue);
+ if (isNaN(value)) {
+ this.log("NaN on setFieldValue!");
+ return;
+ }
+
+ if (aValue.length == aField.maxLength) {
+ let min = Number(aField.getAttribute("min"));
+ let max = Number(aField.getAttribute("max"));
+
+ if (aValue < min) {
+ value = min;
+ } else if (aValue > max) {
+ value = max;
+ }
+ }
+
+ if (aField == this.mMonthField ||
+ aField == this.mDayField) {
+ // prepend zero
+ if (value < 10) {
+ value = "0" + value;
+ }
+ } else {
+ // prepend zeroes
+ if (value < 10) {
+ value = "000" + value;
+ } else if (value < 100) {
+ value = "00" + value;
+ } else if (value < 1000) {
+ value = "0" + value;
+ }
+
+ if (value.toString().length > this.mYearLength &&
+ value.toString().length <= this.mMaxYear.toString().length) {
+ this.mYearField.size = value.toString().length;
+ }
+ }
+
+ aField.value = value;
+ this.updateResetButtonVisibility();
+ ]]>
+ </body>
+ </method>
+
+ <method name="isAnyValueAvailable">
+ <parameter name="aForPicker"/>
+ <body>
+ <![CDATA[
+ return !this.isEmpty(this.mMonthField.value) ||
+ !this.isEmpty(this.mDayField.value) ||
+ !this.isEmpty(this.mYearField.value);
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
<binding id="time-input"
extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base">
<resources>
@@ -45,13 +499,13 @@
this.mMinSecPageUpDownInterval = 10;
this.mHourField =
- document.getAnonymousElementByAttribute(this, "anonid", "input-one");
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-one");
this.mHourField.setAttribute("typeBuffer", "");
this.mMinuteField =
- document.getAnonymousElementByAttribute(this, "anonid", "input-two");
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-two");
this.mMinuteField.setAttribute("typeBuffer", "");
this.mDayPeriodField =
- document.getAnonymousElementByAttribute(this, "anonid", "input-three");
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-three");
this.mDayPeriodField.classList.remove("numeric");
this.mHourField.placeholder = this.mPlaceHolder;
@@ -64,10 +518,10 @@
this.mMinuteField.setAttribute("max", this.mMaxMinute);
this.mMinuteSeparator =
- document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
+ window.document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
this.mMinuteSeparator.textContent = this.mSeparatorText;
this.mSpaceSeparator =
- document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
+ window.document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
// space between time and am/pm field
this.mSpaceSeparator.textContent = " ";
@@ -79,6 +533,7 @@
if (this.mInputElement.value) {
this.setFieldsFromInputValue();
}
+ this.updateResetButtonVisibility();
]]>
</constructor>
@@ -138,7 +593,7 @@
}
this.log("setFieldsFromInputValue: " + value);
- let [hour, minute, second] = value.split(':');
+ let [hour, minute, second] = value.split(":");
this.setFieldValue(this.mHourField, hour);
this.setFieldValue(this.mMinuteField, minute);
@@ -204,6 +659,13 @@
<method name="setInputValueFromFields">
<body>
<![CDATA[
+ if (!this.isAnyValueAvailable(false) && this.mInputElement.value) {
+ // Values in the input box was cleared, clear the input element's
+ // value if not empty.
+ this.mInputElement.setUserInput("");
+ return;
+ }
+
if (this.isEmpty(this.mHourField.value) ||
this.isEmpty(this.mMinuteField.value) ||
(this.mDayPeriodField && this.isEmpty(this.mDayPeriodField.value)) ||
@@ -239,6 +701,10 @@
time += "." + this.mMillisecField.value;
}
+ if (time == this.mInputElement.value) {
+ return;
+ }
+
this.log("setInputValueFromFields: " + time);
this.mInputElement.setUserInput(time);
]]>
@@ -265,6 +731,9 @@
if (!this.isEmpty(minute)) {
this.setFieldValue(this.mMinuteField, minute);
}
+
+ // Update input element's .value if needed.
+ this.setInputValueFromFields();
]]>
</body>
</method>
@@ -282,21 +751,25 @@
if (this.mHourField && !this.mHourField.disabled &&
!this.mHourField.readOnly) {
this.mHourField.value = "";
+ this.mHourField.setAttribute("typeBuffer", "");
}
if (this.mMinuteField && !this.mMinuteField.disabled &&
!this.mMinuteField.readOnly) {
this.mMinuteField.value = "";
+ this.mMinuteField.setAttribute("typeBuffer", "");
}
if (this.mSecondField && !this.mSecondField.disabled &&
!this.mSecondField.readOnly) {
this.mSecondField.value = "";
+ this.mSecondField.setAttribute("typeBuffer", "");
}
if (this.mMillisecField && !this.mMillisecField.disabled &&
!this.mMillisecField.readOnly) {
this.mMillisecField.value = "";
+ this.mMillisecField.setAttribute("typeBuffer", "");
}
if (this.mDayPeriodField && !this.mDayPeriodField.disabled &&
@@ -304,9 +777,11 @@
this.mDayPeriodField.value = "";
}
- if (!aFromInputElement) {
+ if (!aFromInputElement && this.mInputElement.value) {
this.mInputElement.setUserInput("");
}
+
+ this.updateResetButtonVisibility();
]]>
</body>
</method>
@@ -376,6 +851,7 @@
this.mDayPeriodField.value == this.mAMIndicator ?
this.mPMIndicator : this.mAMIndicator;
this.mDayPeriodField.select();
+ this.updateResetButtonVisibility();
this.setInputValueFromFields();
return;
}
@@ -433,6 +909,7 @@
this.mDayPeriodField.value = this.mPMIndicator;
this.mDayPeriodField.select();
}
+ this.updateResetButtonVisibility();
return;
}
@@ -488,16 +965,30 @@
}
aField.value = value;
+ this.updateResetButtonVisibility();
]]>
</body>
</method>
- <method name="isValueAvailable">
+ <method name="isAnyValueAvailable">
+ <parameter name="aForPicker"/>
<body>
<![CDATA[
+ let available = !this.isEmpty(this.mHourField.value) ||
+ !this.isEmpty(this.mMinuteField.value);
+
+ if (available) {
+ return true;
+ }
+
// Picker only cares about hour:minute.
- return !this.isEmpty(this.mHourField.value) ||
- !this.isEmpty(this.mMinuteField.value);
+ if (aForPicker) {
+ return false;
+ }
+
+ return (this.mDayPeriodField && !this.isEmpty(this.mDayPeriodField.value)) ||
+ (this.mSecondField && !this.isEmpty(this.mSecondField.value)) ||
+ (this.mMillisecField && !this.isEmpty(this.mMillisecField.value));
]]>
</body>
</method>
@@ -546,7 +1037,8 @@
<content>
<html:div class="datetime-input-box-wrapper"
xbl:inherits="context,disabled,readonly">
- <html:span>
+ <html:span class="datetime-input-edit-wrapper"
+ anonid="edit-wrapper">
<html:input anonid="input-one"
class="textbox-input datetime-input numeric"
size="2" maxlength="2"
@@ -563,9 +1055,8 @@
xbl:inherits="disabled,readonly,tabindex"/>
</html:span>
- <html:button class="datetime-reset-button" anoid="reset-button"
- tabindex="-1" xbl:inherits="disabled"
- onclick="document.getBindingParent(this).clearInputFields(false);"/>
+ <html:button class="datetime-reset-button" anonid="reset-button"
+ tabindex="-1" xbl:inherits="disabled"/>
</html:div>
</content>
@@ -579,9 +1070,49 @@
this.mMax = this.mInputElement.max;
this.mStep = this.mInputElement.step;
this.mIsPickerOpen = false;
+
+ this.mResetButton =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "reset-button");
+
+ this.EVENTS.forEach((eventName) => {
+ this.addEventListener(eventName, this, { mozSystemGroup: true });
+ });
+ // Handle keypress separately since we need to catch it on capturing.
+ this.addEventListener("keypress", this, {
+ capture: true,
+ mozSystemGroup: true
+ });
+ // This is to open the picker when input element is clicked (this
+ // includes padding area).
+ this.mInputElement.addEventListener("click", this,
+ { mozSystemGroup: true });
]]>
</constructor>
+ <destructor>
+ <![CDATA[
+ this.EVENTS.forEach((eventName) => {
+ this.removeEventListener(eventName, this, { mozSystemGroup: true });
+ });
+ this.removeEventListener("keypress", this, {
+ capture: true,
+ mozSystemGroup: true
+ });
+ this.mInputElement.removeEventListener("click", this,
+ { mozSystemGroup: true });
+
+ this.mInputElement = null;
+ ]]>
+ </destructor>
+
+ <property name="EVENTS" readonly="true">
+ <getter>
+ <![CDATA[
+ return ["focus", "blur", "copy", "cut", "paste", "mousedown"];
+ ]]>
+ </getter>
+ </property>
+
<method name="log">
<parameter name="aMsg"/>
<body>
@@ -593,11 +1124,23 @@
</body>
</method>
+ <method name="updateResetButtonVisibility">
+ <body>
+ <![CDATA[
+ if (this.isAnyValueAvailable(false)) {
+ this.mResetButton.style.visibility = "visible";
+ } else {
+ this.mResetButton.style.visibility = "hidden";
+ }
+ ]]>
+ </body>
+ </method>
+
<method name="focusInnerTextBox">
<body>
<![CDATA[
this.log("focusInnerTextBox");
- document.getAnonymousElementByAttribute(this, "anonid", "input-one").focus();
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-one").focus();
]]>
</body>
</method>
@@ -710,10 +1253,22 @@
</body>
</method>
+ <method name="getCurrentValue">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
+ <method name="isAnyValueAvailable">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
<method name="notifyPicker">
<body>
<![CDATA[
- if (this.mIsPickerOpen && this.isValueAvailable()) {
+ if (this.mIsPickerOpen && this.isAnyValueAvailable(true)) {
this.mInputElement.updateDateTimePicker(this.getCurrentValue());
}
]]>
@@ -736,72 +1291,153 @@
</body>
</method>
- </implementation>
-
- <handlers>
- <handler event="focus">
- <![CDATA[
- this.log("focus on: " + event.originalTarget);
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("handleEvent: " + aEvent.type);
- let target = event.originalTarget;
- if (target.type == "text") {
- this.mLastFocusedField = target;
- target.select();
- }
- ]]>
- </handler>
+ switch (aEvent.type) {
+ case "keypress": {
+ this.onKeyPress(aEvent);
+ break;
+ }
+ case "click": {
+ this.onClick(aEvent);
+ break;
+ }
+ case "focus": {
+ this.onFocus(aEvent);
+ break;
+ }
+ case "blur": {
+ this.onBlur(aEvent);
+ break;
+ }
+ case "mousedown": {
+ if (aEvent.originalTarget == this.mResetButton) {
+ aEvent.preventDefault();
+ }
+ break;
+ }
+ case "copy":
+ case "cut":
+ case "paste": {
+ aEvent.preventDefault();
+ break;
+ }
+ default:
+ break;
+ }
+ ]]>
+ </body>
+ </method>
- <handler event="blur">
- <![CDATA[
- this.setInputValueFromFields();
- ]]>
- </handler>
+ <method name="onFocus">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("onFocus originalTarget: " + aEvent.originalTarget);
- <handler event="click">
- <![CDATA[
- // XXX: .originalTarget is not expected.
- // When clicking on one of the inner text boxes, the .originalTarget is
- // a HTMLDivElement and when clicking on the reset button, it's a
- // HTMLButtonElement but it's not equal to our reset-button.
- this.log("click on: " + event.originalTarget);
- if (event.defaultPrevented || this.isDisabled() || this.isReadonly()) {
- return;
- }
+ let target = aEvent.originalTarget;
+ if ((target instanceof HTMLInputElement) && target.type == "text") {
+ this.mLastFocusedField = target;
+ target.select();
+ }
+ ]]>
+ </body>
+ </method>
- if (!(event.originalTarget instanceof HTMLButtonElement)) {
- this.mInputElement.openDateTimePicker(this.getCurrentValue());
- }
- ]]>
- </handler>
+ <method name="onBlur">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("onBlur originalTarget: " + aEvent.originalTarget +
+ " target: " + aEvent.target);
- <handler event="keypress" phase="capturing">
- <![CDATA[
- let key = event.key;
- this.log("keypress: " + key);
+ let target = aEvent.originalTarget;
+ target.setAttribute("typeBuffer", "");
+ this.setInputValueFromFields();
+ ]]>
+ </body>
+ </method>
- if (key == "Backspace" || key == "Tab") {
- return;
- }
+ <method name="onKeyPress">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("onKeyPress key: " + aEvent.key);
+
+ switch (aEvent.key) {
+ // Close picker on Enter, Escape or Space key.
+ case "Enter":
+ case "Escape":
+ case " ": {
+ if (this.mIsPickerOpen) {
+ this.mInputElement.closeDateTimePicker();
+ aEvent.preventDefault();
+ }
+ break;
+ }
+ case "Backspace": {
+ let targetField = aEvent.originalTarget;
+ targetField.value = "";
+ targetField.setAttribute("typeBuffer", "");
+ this.updateResetButtonVisibility();
+ this.setInputValueFromFields();
+ aEvent.preventDefault();
+ break;
+ }
+ case "ArrowRight":
+ case "ArrowLeft": {
+ this.advanceToNextField(aEvent.key == "ArrowRight" ? false : true);
+ aEvent.preventDefault();
+ break;
+ }
+ case "ArrowUp":
+ case "ArrowDown":
+ case "PageUp":
+ case "PageDown":
+ case "Home":
+ case "End": {
+ this.handleKeyboardNav(aEvent);
+ aEvent.preventDefault();
+ break;
+ }
+ default: {
+ // printable characters
+ if (aEvent.keyCode == 0 &&
+ !(aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey)) {
+ this.handleKeypress(aEvent);
+ aEvent.preventDefault();
+ }
+ break;
+ }
+ }
+ ]]>
+ </body>
+ </method>
- if (key == "Enter" || key == " ") {
- // Close picker on Enter and Space.
- this.mInputElement.closeDateTimePicker();
- }
+ <method name="onClick">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("onClick originalTarget: " + aEvent.originalTarget +
+ " target: " + aEvent.target);
- if (key == "ArrowUp" || key == "ArrowDown" ||
- key == "PageUp" || key == "PageDown" ||
- key == "Home" || key == "End") {
- this.handleKeyboardNav(event);
- } else if (key == "ArrowRight" || key == "ArrowLeft") {
- this.advanceToNextField((key == "ArrowRight" ? false : true));
- } else {
- this.handleKeypress(event);
- }
+ if (aEvent.defaultPrevented || this.isDisabled() || this.isReadonly()) {
+ return;
+ }
- event.preventDefault();
- ]]>
- </handler>
- </handlers>
+ if (aEvent.originalTarget == this.mResetButton) {
+ this.clearInputFields(false);
+ } else if (!this.mIsPickerOpen) {
+ this.mInputElement.openDateTimePicker(this.getCurrentValue());
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
</binding>
</bindings>
diff --git a/toolkit/content/widgets/datetimepicker.xml b/toolkit/content/widgets/datetimepicker.xml
index 5f16f1ff0..1d6a5e772 100644
--- a/toolkit/content/widgets/datetimepicker.xml
+++ b/toolkit/content/widgets/datetimepicker.xml
@@ -999,13 +999,13 @@
<body>
<![CDATA[
var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory";
- var dtfMonth = Intl.DateTimeFormat(locale, {month: "long"});
+ var dtfMonth = Intl.DateTimeFormat(locale, {month: "long", timeZone: "UTC"});
var dtfWeekday = Intl.DateTimeFormat(locale, {weekday: "narrow"});
var monthLabel = this.monthField.firstChild;
- var tempDate = new Date(2005, 0, 1);
+ var tempDate = new Date(Date.UTC(2005, 0, 1));
for (var month = 0; month < 12; month++) {
- tempDate.setMonth(month);
+ tempDate.setUTCMonth(month);
monthLabel.setAttribute("value", dtfMonth.format(tempDate));
monthLabel = monthLabel.nextSibling;
}
diff --git a/toolkit/content/widgets/datetimepopup.xml b/toolkit/content/widgets/datetimepopup.xml
index 327f45368..b4335e1ce 100644
--- a/toolkit/content/widgets/datetimepopup.xml
+++ b/toolkit/content/widgets/datetimepopup.xml
@@ -11,17 +11,31 @@
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="datetime-popup"
extends="chrome://global/content/bindings/popup.xml#arrowpanel">
+ <resources>
+ <stylesheet src="chrome://global/skin/datetimepopup.css"/>
+ </resources>
<implementation>
<field name="dateTimePopupFrame">
this.querySelector("#dateTimePopupFrame");
</field>
<field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field>
<field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
- <method name="loadPicker">
+ <field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field>
+ <field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field>
+ <constructor><![CDATA[
+ this.l10n = {};
+ const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
+ .getService(Components.interfaces.mozIMozIntl);
+ mozIntl.addGetCalendarInfo(l10n);
+ mozIntl.addGetDisplayNames(l10n);
+ // Notify DateTimePickerHelper.jsm that binding is ready.
+ this.dispatchEvent(new CustomEvent("DateTimePickerBindingReady"));
+ ]]></constructor>
+ <method name="openPicker">
<parameter name="type"/>
+ <parameter name="anchor"/>
<parameter name="detail"/>
<body><![CDATA[
- this.hidden = false;
this.type = type;
this.pickerState = {};
// TODO: Resize picker according to content zoom level
@@ -35,18 +49,28 @@
this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
break;
}
+ case "date": {
+ this.detail = detail;
+ this.dateTimePopupFrame.addEventListener("load", this, true);
+ this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/datepicker.xhtml");
+ this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
+ this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
+ break;
+ }
}
+ this.hidden = false;
+ this.openPopup(anchor, "after_start", 0, 0);
]]></body>
</method>
<method name="closePicker">
<body><![CDATA[
- this.hidden = true;
this.setInputBoxValue(true);
this.pickerState = {};
this.type = undefined;
this.dateTimePopupFrame.removeEventListener("load", this, true);
- this.dateTimePopupFrame.contentDocument.removeEventListener("TimePickerPopupChanged", this, false);
+ this.dateTimePopupFrame.contentDocument.removeEventListener("message", this, false);
this.dateTimePopupFrame.setAttribute("src", "");
+ this.hidden = true;
]]></body>
</method>
<method name="setPopupValue">
@@ -55,25 +79,39 @@
switch (this.type) {
case "time": {
this.postMessageToPicker({
- name: "TimePickerSetValue",
+ name: "PickerSetValue",
detail: data.value
});
break;
}
+ case "date": {
+ const { year, month, day } = data.value;
+ this.postMessageToPicker({
+ name: "PickerSetValue",
+ detail: {
+ year,
+ // Month value from input box starts from 1 instead of 0
+ month: month == undefined ? undefined : month - 1,
+ day
+ }
+ });
+ break;
+ }
}
]]></body>
</method>
<method name="initPicker">
<parameter name="detail"/>
<body><![CDATA[
+ const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global");
+
switch (this.type) {
case "time": {
const { hour, minute } = detail.value;
const format = detail.format || "12";
- const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global");
this.postMessageToPicker({
- name: "TimePickerInit",
+ name: "PickerInit",
detail: {
hour,
minute,
@@ -86,6 +124,56 @@
});
break;
}
+ case "date": {
+ const { year, month, day } = detail.value;
+ const { firstDayOfWeek, weekends } =
+ this.getCalendarInfo(locale);
+ const monthStrings = this.getDisplayNames(
+ locale, [
+ "dates/gregorian/months/january",
+ "dates/gregorian/months/february",
+ "dates/gregorian/months/march",
+ "dates/gregorian/months/april",
+ "dates/gregorian/months/may",
+ "dates/gregorian/months/june",
+ "dates/gregorian/months/july",
+ "dates/gregorian/months/august",
+ "dates/gregorian/months/september",
+ "dates/gregorian/months/october",
+ "dates/gregorian/months/november",
+ "dates/gregorian/months/december",
+ ], "short");
+ const weekdayStrings = this.getDisplayNames(
+ locale, [
+ "dates/gregorian/weekdays/sunday",
+ "dates/gregorian/weekdays/monday",
+ "dates/gregorian/weekdays/tuesday",
+ "dates/gregorian/weekdays/wednesday",
+ "dates/gregorian/weekdays/thursday",
+ "dates/gregorian/weekdays/friday",
+ "dates/gregorian/weekdays/saturday",
+ ], "short");
+
+ this.postMessageToPicker({
+ name: "PickerInit",
+ detail: {
+ year,
+ // Month value from input box starts from 1 instead of 0
+ month: month == undefined ? undefined : month - 1,
+ day,
+ firstDayOfWeek,
+ weekends,
+ monthStrings,
+ weekdayStrings,
+ locale,
+ min: detail.min,
+ max: detail.max,
+ step: detail.step,
+ stepBase: detail.stepBase,
+ }
+ });
+ break;
+ }
}
]]></body>
</method>
@@ -109,6 +197,10 @@
}
break;
}
+ case "date": {
+ this.sendPickerValueChanged(this.pickerState);
+ break;
+ }
}
]]></body>
</method>
@@ -125,9 +217,60 @@
}));
break;
}
+ case "date": {
+ this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
+ detail: {
+ year: value.year,
+ // Month value from input box starts from 1 instead of 0
+ month: value.month == undefined ? undefined : value.month + 1,
+ day: value.day
+ }
+ }));
+ break;
+ }
}
]]></body>
</method>
+ <method name="getCalendarInfo">
+ <parameter name="locale"/>
+ <body><![CDATA[
+ const calendarInfo = this.l10n.getCalendarInfo(locale);
+
+ // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
+ // so they need to be mapped to JavaScript convention with 0 as Sunday
+ // and 6 as Saturday
+ let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
+ weekendStart = calendarInfo.weekendStart - 1,
+ weekendEnd = calendarInfo.weekendEnd - 1;
+
+ let weekends = [];
+
+ // Make sure weekendEnd is greater than weekendStart
+ if (weekendEnd < weekendStart) {
+ weekendEnd += 7;
+ }
+
+ // We get the weekends by incrementing weekendStart up to weekendEnd.
+ // If the start and end is the same day, then weekends only has one day.
+ for (let day = weekendStart; day <= weekendEnd; day++) {
+ weekends.push(day % 7);
+ }
+
+ return {
+ firstDayOfWeek,
+ weekends
+ }
+ ]]></body>
+ </method>
+ <method name="getDisplayNames">
+ <parameter name="locale"/>
+ <parameter name="keys"/>
+ <parameter name="style"/>
+ <body><![CDATA[
+ const displayNames = this.l10n.getDisplayNames(locale, {keys, style});
+ return keys.map(key => displayNames.values[key]);
+ ]]></body>
+ </method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
@@ -152,11 +295,16 @@
}
switch (aEvent.data.name) {
- case "TimePickerPopupChanged": {
+ case "PickerPopupChanged": {
this.pickerState = aEvent.data.detail;
this.setInputBoxValue();
break;
}
+ case "ClosePopup": {
+ this.hidePopup();
+ this.closePicker();
+ break;
+ }
}
]]></body>
</method>
@@ -170,12 +318,5 @@
</method>
</implementation>
- <handlers>
- <handler event="popuphiding">
- <![CDATA[
- this.closePicker();
- ]]>
- </handler>
- </handlers>
</binding>
</bindings>
diff --git a/toolkit/content/widgets/spinner.js b/toolkit/content/widgets/spinner.js
index 208ab1931..4901320b5 100644
--- a/toolkit/content/widgets/spinner.js
+++ b/toolkit/content/widgets/spinner.js
@@ -98,7 +98,7 @@ function Spinner(props, context) {
setState(newState) {
const { spinner } = this.elements;
const { value, items } = this.state;
- const { value: newValue, items: newItems, isValueSet, isInvalid } = newState;
+ const { value: newValue, items: newItems, isValueSet, isInvalid, smoothScroll = true } = newState;
if (this._isArrayDiff(newItems, items)) {
this.state = Object.assign(this.state, newState);
@@ -106,23 +106,23 @@ function Spinner(props, context) {
this._scrollTo(newValue, true);
} else if (newValue != value) {
this.state = Object.assign(this.state, newState);
- this._smoothScrollTo(newValue);
- }
-
- if (isValueSet) {
- if (isInvalid) {
- this._removeSelection();
+ if (smoothScroll) {
+ this._smoothScrollTo(newValue, true);
} else {
- this._updateSelection();
+ this._scrollTo(newValue, true);
}
}
+
+ if (isValueSet && !isInvalid) {
+ this._updateSelection();
+ } else {
+ this._removeSelection();
+ }
},
/**
* Whenever scroll event is detected:
* - Update the index state
- * - If a smooth scroll has reached its destination, set [isScrolling] state
- * to false
* - If the value has changed, update the [value] state and call [setValue]
* - If infinite scrolling is on, reset the scrolling position if necessary
*/
@@ -135,14 +135,8 @@ function Spinner(props, context) {
const value = itemsView[this.state.index + viewportTopOffset].value;
- // Check if smooth scrolling has reached its destination.
- // This prevents input box jump when input box changes values.
- if (this.state.value == value && this.state.isScrolling) {
- this.state.isScrolling = false;
- }
-
- // Call setValue if value has changed, and is not smooth scrolling
- if (this.state.value != value && !this.state.isScrolling) {
+ // Call setValue if value has changed
+ if (this.state.value != value) {
this.state.value = value;
this.props.setValue(value);
}
@@ -266,11 +260,11 @@ function Spinner(props, context) {
* Attach event listeners to the spinner and buttons.
*/
_attachEventListeners() {
- const { spinner } = this.elements;
+ const { spinner, container } = this.elements;
spinner.addEventListener("scroll", this, { passive: true });
- document.addEventListener("mouseup", this, { passive: true });
- document.addEventListener("mousedown", this);
+ container.addEventListener("mouseup", this, { passive: true });
+ container.addEventListener("mousedown", this, { passive: true });
},
/**
@@ -288,9 +282,6 @@ function Spinner(props, context) {
break;
}
case "mousedown": {
- // Use preventDefault to keep focus on input boxes
- event.preventDefault();
- event.target.setCapture();
this.state.mouseState = {
down: true,
layerX: event.layerX,
@@ -300,11 +291,11 @@ function Spinner(props, context) {
// An "active" class is needed to simulate :active pseudo-class
// because element is not focused.
event.target.classList.add("active");
- this._smoothScrollToIndex(index + 1);
+ this._smoothScrollToIndex(index - 1);
}
if (event.target == down) {
event.target.classList.add("active");
- this._smoothScrollToIndex(index - 1);
+ this._smoothScrollToIndex(index + 1);
}
if (event.target.parentNode == spinner) {
// Listen to dragging events
@@ -444,10 +435,6 @@ function Spinner(props, context) {
_smoothScrollToIndex(index) {
const element = this.elements.spinner.children[index];
if (element) {
- // Set the isScrolling flag before smooth scrolling begins
- // and remove it when it has reached the destination.
- // This prevents input box jump when input box changes values
- this.state.isScrolling = true;
element.scrollIntoView({
behavior: "smooth", block: "start"
});
diff --git a/toolkit/content/widgets/timekeeper.js b/toolkit/content/widgets/timekeeper.js
index 2234c9e50..3b4e7eb0a 100644
--- a/toolkit/content/widgets/timekeeper.js
+++ b/toolkit/content/widgets/timekeeper.js
@@ -14,7 +14,7 @@
* {
* {Date} min
* {Date} max
- * {Number} stepInMs
+ * {Number} step
* {String} format: Either "12" or "24"
* }
*/
@@ -286,15 +286,15 @@ function TimeKeeper(props) {
* }
*/
_getSteps(startValue, endValue, minStep, formatter) {
- const { min, max, stepInMs } = this.props;
+ const { min, max, step } = this.props;
// The timeStep should be big enough so that there won't be
// duplications. Ex: minimum step for minute should be 60000ms,
// if smaller than that, next step might return the same minute.
- const timeStep = Math.max(minStep, stepInMs);
+ const timeStep = Math.max(minStep, step);
// Make sure the starting point and end point is not off step
let time = min.valueOf() + Math.ceil((startValue - min.valueOf()) / timeStep) * timeStep;
- let maxValue = min.valueOf() + Math.floor((max.valueOf() - min.valueOf()) / stepInMs) * stepInMs;
+ let maxValue = min.valueOf() + Math.floor((max.valueOf() - min.valueOf()) / step) * step;
let steps = [];
// Increment by timeStep until reaching the end of the range.
@@ -410,9 +410,9 @@ function TimeKeeper(props) {
* @return {Boolean}
*/
_isOffStep(time) {
- const { min, stepInMs } = this.props;
+ const { min, step } = this.props;
- return (time.valueOf() - min.valueOf()) % stepInMs != 0;
+ return (time.valueOf() - min.valueOf()) % step != 0;
}
};
}
diff --git a/toolkit/content/widgets/timepicker.js b/toolkit/content/widgets/timepicker.js
index f438e9ec6..1f0463fe4 100644
--- a/toolkit/content/widgets/timepicker.js
+++ b/toolkit/content/widgets/timepicker.js
@@ -13,8 +13,6 @@ function TimePicker(context) {
const debug = 0 ? console.log.bind(console, "[timepicker]") : function() {};
const DAY_PERIOD_IN_HOURS = 12,
- SECOND_IN_MS = 1000,
- MINUTE_IN_MS = 60000,
DAY_IN_MS = 86400000;
TimePicker.prototype = {
@@ -24,9 +22,9 @@ function TimePicker(context) {
* {
* {Number} hour [optional]: Hour in 24 hours format (0~23), default is current hour
* {Number} minute [optional]: Minute (0~59), default is current minute
- * {String} min [optional]: Minimum time, in 24 hours format. ex: "05:45"
- * {String} max [optional]: Maximum time, in 24 hours format. ex: "23:00"
- * {Number} step [optional]: Step size in minutes. Default is 60.
+ * {Number} min: Minimum time, in ms
+ * {Number} max: Maximum time, in ms
+ * {Number} step: Step size in ms
* {String} format [optional]: "12" for 12 hours, "24" for 24 hours format
* {String} locale [optional]: User preferred locale
* }
@@ -51,11 +49,10 @@ function TimePicker(context) {
let timerHour = hour == undefined ? now.getHours() : hour;
let timerMinute = minute == undefined ? now.getMinutes() : minute;
- // The spec defines 1 step == 1 second, need to convert to ms for timekeeper
let timeKeeper = new TimeKeeper({
- min: this._parseTimeString(min) || new Date(0),
- max: this._parseTimeString(max) || new Date(DAY_IN_MS - 1),
- stepInMs: step ? step * SECOND_IN_MS : MINUTE_IN_MS,
+ min: new Date(Number.isNaN(min) ? 0 : min),
+ max: new Date(Number.isNaN(max) ? DAY_IN_MS - 1 : max),
+ step,
format: format || "12"
});
timeKeeper.setState({ hour: timerHour, minute: timerMinute });
@@ -64,17 +61,6 @@ function TimePicker(context) {
},
/**
- * Convert a time string from DOM attribute to a date object.
- *
- * @param {String} timeString: (ex. "10:30", "23:55", "12:34:56.789")
- * @return {Date/Boolean} Date object or false if date is invalid.
- */
- _parseTimeString(timeString) {
- let time = new Date("1970-01-01T" + timeString + "Z");
- return time.toString() == "Invalid Date" ? false : time;
- },
-
- /**
* Initalize the spinner components.
*/
_createComponents() {
@@ -206,7 +192,7 @@ function TimePicker(context) {
// The panel is listening to window for postMessage event, so we
// do postMessage to itself to send data to input boxes.
window.postMessage({
- name: "TimePickerPopupChanged",
+ name: "PickerPopupChanged",
detail: {
hour,
minute,
@@ -218,6 +204,7 @@ function TimePicker(context) {
},
_attachEventListeners() {
window.addEventListener("message", this);
+ document.addEventListener("mousedown", this);
},
/**
@@ -231,6 +218,12 @@ function TimePicker(context) {
this.handleMessage(event);
break;
}
+ case "mousedown": {
+ // Use preventDefault to keep focus on input boxes
+ event.preventDefault();
+ event.target.setCapture();
+ break;
+ }
}
},
@@ -241,11 +234,11 @@ function TimePicker(context) {
*/
handleMessage(event) {
switch (event.data.name) {
- case "TimePickerSetValue": {
+ case "PickerSetValue": {
this.set(event.data.detail);
break;
}
- case "TimePickerInit": {
+ case "PickerInit": {
this.init(event.data.detail);
break;
}
diff --git a/toolkit/content/widgets/toolbar.xml b/toolkit/content/widgets/toolbar.xml
index 548504e24..e1f58f7aa 100644
--- a/toolkit/content/widgets/toolbar.xml
+++ b/toolkit/content/widgets/toolbar.xml
@@ -30,7 +30,7 @@
</field>
<field name="externalToolbars">
- []
+ []
</field>
<!-- Set by customizeToolbar.js -->
@@ -49,18 +49,54 @@
<constructor>
<![CDATA[
+ this.toolbarInfoSeparators = ["|", "-"];
+ this.toolbarInfoLegacySeparator = ":";
// Look to see if there is a toolbarset.
this.toolbarset = this.firstChild;
- while (this.toolbarset && this.toolbarset.localName != "toolbarset")
+ while (this.toolbarset && this.toolbarset.localName != "toolbarset") {
this.toolbarset = toolbarset.nextSibling;
+ }
if (this.toolbarset) {
// Create each toolbar described by the toolbarset.
var index = 0;
- while (toolbarset.hasAttribute("toolbar"+(++index))) {
- var toolbarInfo = toolbarset.getAttribute("toolbar"+index);
- var infoSplit = toolbarInfo.split(":");
- this.appendCustomToolbar(infoSplit[0], infoSplit[1]);
+ while (this.toolbarset.hasAttribute("toolbar" + (++index))) {
+ let hiddingAttribute =
+ this.toolbarset.getAttribute("type") == "menubar"
+ ? "autohide" : "collapsed";
+ let toolbarInfo = this.toolbarset.getAttribute("toolbar" + index);
+ let infoSplit = toolbarInfo.split(this.toolbarInfoSeparators[0]);
+ if (infoSplit.length == 1) {
+ infoSplit = toolbarInfo.split(this.toolbarInfoLegacySeparator);
+ }
+ let infoName = infoSplit[0];
+ let infoHidingAttribute = [null, null];
+ let infoCurrentSet = "";
+ let infoSplitLen = infoSplit.length;
+ switch (infoSplitLen) {
+ case 3:
+ // Pale Moon 27.2+
+ // Basilisk (UXP)
+ infoHidingAttribute = infoSplit[1]
+ .split(this.toolbarInfoSeparators[1]);
+ infoCurrentSet = infoSplit[2];
+ break;
+ case 2:
+ // Legacy:
+ // - toolbars from Pale Moon 27.0 - 27.1.x
+ // - Basilisk (moebius)
+ // The previous value (hiddingAttribute) isn't stored.
+ infoHidingAttribute = [hiddingAttribute, "false"];
+ infoCurrentSet = infoSplit[1];
+ break;
+ default:
+ Components.utils.reportError(
+ "Customizable toolbars - an invalid value:" + "\n"
+ + '"toolbar' + index + '" = "' + toolbarInfo + '"');
+ break;
+ }
+ this.appendCustomToolbar(
+ infoName, infoCurrentSet, infoHidingAttribute);
}
}
]]>
@@ -69,6 +105,7 @@
<method name="appendCustomToolbar">
<parameter name="aName"/>
<parameter name="aCurrentSet"/>
+ <parameter name="aHidingAttribute"/>
<body>
<![CDATA[
if (!this.toolbarset)
@@ -84,6 +121,10 @@
toolbar.setAttribute("iconsize", this.getAttribute("iconsize"));
toolbar.setAttribute("context", this.toolbarset.getAttribute("context"));
toolbar.setAttribute("class", "chromeclass-toolbar");
+ // Restore persist the hiding attribute.
+ if (aHidingAttribute[0]) {
+ toolbar.setAttribute(aHidingAttribute[0], aHidingAttribute[1]);
+ }
this.insertBefore(toolbar, this.toolbarset);
return toolbar;
diff --git a/toolkit/locales/en-US/chrome/global/customizeToolbar.properties b/toolkit/locales/en-US/chrome/global/customizeToolbar.properties
index 0ec6d2c1d..b19152fab 100644
--- a/toolkit/locales/en-US/chrome/global/customizeToolbar.properties
+++ b/toolkit/locales/en-US/chrome/global/customizeToolbar.properties
@@ -5,6 +5,7 @@
enterToolbarTitle=New Toolbar
enterToolbarName=Enter a name for this toolbar:
enterToolbarDup=There is already a toolbar with the name “%S”. Please enter a different name.
+enterToolbarIllegalChars=The name contains illegal character "|". Please enter a different name.
enterToolbarBlank=You must enter a name to create a new toolbar.
separatorTitle=Separator
springTitle=Flexible Space
diff --git a/toolkit/locales/en-US/chrome/global/datetimebox.dtd b/toolkit/locales/en-US/chrome/global/datetimebox.dtd
new file mode 100644
index 000000000..0deffa6b3
--- /dev/null
+++ b/toolkit/locales/en-US/chrome/global/datetimebox.dtd
@@ -0,0 +1,9 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- Placeholders for input type=date -->
+
+<!ENTITY date.year.placeholder "yyyy">
+<!ENTITY date.month.placeholder "mm">
+<!ENTITY date.day.placeholder "dd">
diff --git a/toolkit/locales/jar.mn b/toolkit/locales/jar.mn
index e49e978f5..abc96086f 100644
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -39,6 +39,7 @@
locale/@AB_CD@/global/customizeToolbar.dtd (%chrome/global/customizeToolbar.dtd)
locale/@AB_CD@/global/customizeToolbar.properties (%chrome/global/customizeToolbar.properties)
#endif
+ locale/@AB_CD@/global/datetimebox.dtd (%chrome/global/datetimebox.dtd)
locale/@AB_CD@/global/datetimepicker.dtd (%chrome/global/datetimepicker.dtd)
locale/@AB_CD@/global/dateFormat.properties (%chrome/global/dateFormat.properties)
locale/@AB_CD@/global/dialogOverlay.dtd (%chrome/global/dialogOverlay.dtd)
diff --git a/toolkit/modules/DateTimePickerHelper.jsm b/toolkit/modules/DateTimePickerHelper.jsm
index 398687988..b509742b0 100644
--- a/toolkit/modules/DateTimePickerHelper.jsm
+++ b/toolkit/modules/DateTimePickerHelper.jsm
@@ -21,6 +21,7 @@ this.EXPORTED_SYMBOLS = [
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
/*
* DateTimePickerHelper receives message from content side (input box) and
@@ -63,9 +64,13 @@ this.DateTimePickerHelper = {
return;
}
this.picker.closePicker();
+ this.close();
break;
}
case "FormDateTime:UpdatePicker": {
+ if (!this.picker) {
+ return;
+ }
this.picker.setPopupValue(aMessage.data);
break;
}
@@ -87,6 +92,7 @@ this.DateTimePickerHelper = {
if (browser) {
browser.messageManager.sendAsyncMessage("FormDateTime:PickerClosed");
}
+ this.picker.closePicker();
this.close();
break;
}
@@ -97,18 +103,15 @@ this.DateTimePickerHelper = {
// Called when picker value has changed, notify input box about it.
updateInputBoxValue: function(aEvent) {
- // TODO: parse data based on input type.
- const { hour, minute } = aEvent.detail;
- debug("hour: " + hour + ", minute: " + minute);
let browser = this.weakBrowser ? this.weakBrowser.get() : null;
if (browser) {
browser.messageManager.sendAsyncMessage(
- "FormDateTime:PickerValueChanged", { hour, minute });
+ "FormDateTime:PickerValueChanged", aEvent.detail);
}
},
// Get picker from browser and show it anchored to the input box.
- showPicker: function(aBrowser, aData) {
+ showPicker: Task.async(function* (aBrowser, aData) {
let rect = aData.rect;
let dir = aData.dir;
let type = aData.type;
@@ -138,13 +141,23 @@ this.DateTimePickerHelper = {
debug("aBrowser.dateTimePicker not found, exiting now.");
return;
}
- this.picker.loadPicker(type, detail);
+ // The datetimepopup binding is only attached when it is needed.
+ // Check if openPicker method is present to determine if binding has
+ // been attached. If not, attach the binding first before calling it.
+ if (!this.picker.openPicker) {
+ let bindingPromise = new Promise(resolve => {
+ this.picker.addEventListener("DateTimePickerBindingReady",
+ resolve, {once: true});
+ });
+ this.picker.setAttribute("active", true);
+ yield bindingPromise;
+ }
// The arrow panel needs an anchor to work. The popupAnchor (this._anchor)
// is a transparent div that the arrow can point to.
- this.picker.openPopup(this._anchor, "after_start", rect.left, rect.top);
+ this.picker.openPicker(type, this._anchor, detail);
this.addPickerListeners();
- },
+ }),
// Picker is closed, do some cleanup.
close: function() {
diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm
index 681c4240a..3913c2088 100644
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -88,9 +88,9 @@ Cu.import("resource://gre/modules/Log.jsm");
// Configure a logger at the parent 'addons' level to format
// messages for all the modules under addons.*
const PARENT_LOGGER_ID = "addons";
-let parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID);
+var parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID);
parentLogger.level = Log.Level.Warn;
-let formatter = new Log.BasicFormatter();
+var formatter = new Log.BasicFormatter();
// Set parent logger (and its children) to append to
// the Javascript section of the Browser Console
parentLogger.addAppender(new Log.ConsoleAppender(formatter));
@@ -101,7 +101,7 @@ parentLogger.addAppender(new Log.DumpAppender(formatter));
// Create a new logger (child of 'addons' logger)
// for use by the Addons Manager
const LOGGER_ID = "addons.manager";
-let logger = Log.repository.getLogger(LOGGER_ID);
+var logger = Log.repository.getLogger(LOGGER_ID);
// Provide the ability to enable/disable logging
// messages at runtime.
diff --git a/toolkit/mozapps/extensions/DeferredSave.jsm b/toolkit/mozapps/extensions/DeferredSave.jsm
index d7f5b8864..7587ce83b 100644
--- a/toolkit/mozapps/extensions/DeferredSave.jsm
+++ b/toolkit/mozapps/extensions/DeferredSave.jsm
@@ -12,7 +12,7 @@ Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
// Make it possible to mock out timers for testing
-let MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+var MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.EXPORTED_SYMBOLS = ["DeferredSave"];
@@ -23,9 +23,9 @@ Cu.import("resource://gre/modules/Log.jsm");
//Configure a logger at the parent 'DeferredSave' level to format
//messages for all the modules under DeferredSave.*
const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave";
-let parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID);
+var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID);
parentLogger.level = Log.Level.Warn;
-let formatter = new Log.BasicFormatter();
+var formatter = new Log.BasicFormatter();
//Set parent logger (and its children) to append to
//the Javascript section of the Browser Console
parentLogger.addAppender(new Log.ConsoleAppender(formatter));
diff --git a/toolkit/mozapps/extensions/addonManager.js b/toolkit/mozapps/extensions/addonManager.js
index 862b1ea69..731e70c6c 100644
--- a/toolkit/mozapps/extensions/addonManager.js
+++ b/toolkit/mozapps/extensions/addonManager.js
@@ -31,9 +31,9 @@ const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
-let gSingleton = null;
+var gSingleton = null;
-let gParentMM = null;
+var gParentMM = null;
function amManager() {
diff --git a/toolkit/mozapps/extensions/amInstallTrigger.js b/toolkit/mozapps/extensions/amInstallTrigger.js
index b83cbe60b..a18fe84c4 100644
--- a/toolkit/mozapps/extensions/amInstallTrigger.js
+++ b/toolkit/mozapps/extensions/amInstallTrigger.js
@@ -18,7 +18,7 @@ const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
-let log = Log.repository.getLogger("AddonManager.InstallTrigger");
+var log = Log.repository.getLogger("AddonManager.InstallTrigger");
log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"];
function CallbackObject(id, callback, urls, mediator) {
diff --git a/toolkit/mozapps/extensions/amWebInstallListener.js b/toolkit/mozapps/extensions/amWebInstallListener.js
index 901beef07..ac6e2495d 100644
--- a/toolkit/mozapps/extensions/amWebInstallListener.js
+++ b/toolkit/mozapps/extensions/amWebInstallListener.js
@@ -37,7 +37,7 @@ const LOGGER_ID = "addons.weblistener";
// Create a new logger for use by the Addons Web Listener
// (Requires AddonManager.jsm)
-let logger = Log.repository.getLogger(LOGGER_ID);
+var logger = Log.repository.getLogger(LOGGER_ID);
function notifyObservers(aTopic, aBrowser, aUri, aInstalls) {
let info = {
diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js
index 6f2a47482..8d9c132e6 100644
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -2456,7 +2456,7 @@ var gSearchView = {
this._allResultsLink.setAttribute("href",
AddonRepository.getSearchURL(this._lastQuery));
this._allResultsLink.hidden = false;
- },
+ },
updateListAttributes: function gSearchView_updateListAttributes() {
var item = this._listBox.querySelector("richlistitem[remote='true'][first]");
diff --git a/toolkit/mozapps/extensions/content/update.js b/toolkit/mozapps/extensions/content/update.js
index 3d87f4d4b..afc74dca8 100644
--- a/toolkit/mozapps/extensions/content/update.js
+++ b/toolkit/mozapps/extensions/content/update.js
@@ -22,7 +22,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modul
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log", "resource://gre/modules/Log.jsm");
-let logger = null;
+var logger = null;
var gUpdateWizard = {
// When synchronizing app compatibility info this contains all installed
@@ -169,7 +169,7 @@ var gOfflinePage = {
}
// Addon listener to count addons enabled/disabled by metadata checks
-let listener = {
+var listener = {
onDisabled: function listener_onDisabled(aAddon) {
gUpdateWizard.affectedAddonIDs.add(aAddon.id);
gUpdateWizard.metadataDisabled++;
diff --git a/toolkit/mozapps/extensions/internal/AddonRepository.jsm b/toolkit/mozapps/extensions/internal/AddonRepository.jsm
index adcecbee7..76a7528c7 100644
--- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm
@@ -72,7 +72,7 @@ const LOGGER_ID = "addons.repository";
// Create a new logger for use by the Addons Repository
// (Requires AddonManager.jsm)
-let logger = Log.repository.getLogger(LOGGER_ID);
+var logger = Log.repository.getLogger(LOGGER_ID);
// A map between XML keys to AddonSearchResult keys for string values
// that require no extra parsing from XML
@@ -101,7 +101,7 @@ const INTEGER_KEY_MAP = {
};
// Wrap the XHR factory so that tests can override with a mock
-let XHRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1",
+var XHRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1",
"nsIXMLHttpRequest");
function convertHTMLToPlainText(html) {
diff --git a/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm b/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm
index 128146bbe..11944ddf5 100644
--- a/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm
@@ -30,7 +30,7 @@ const LOGGER_ID = "addons.repository.sqlmigrator";
// Create a new logger for use by the Addons Repository SQL Migrator
// (Requires AddonManager.jsm)
-let logger = Log.repository.getLogger(LOGGER_ID);
+var logger = Log.repository.getLogger(LOGGER_ID);
this.EXPORTED_SYMBOLS = ["AddonRepository_SQLiteMigrator"];
diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
index d68a0f175..939e2e269 100644
--- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
@@ -52,7 +52,7 @@ const LOGGER_ID = "addons.update-checker";
// Create a new logger for use by the Addons Update Checker
// (Requires AddonManager.jsm)
-let logger = Log.repository.getLogger(LOGGER_ID);
+var logger = Log.repository.getLogger(LOGGER_ID);
/**
* A serialisation method for RDF data that produces an identical string
diff --git a/toolkit/mozapps/extensions/internal/Content.js b/toolkit/mozapps/extensions/internal/Content.js
index 29c0ed8ce..61a8b0323 100644
--- a/toolkit/mozapps/extensions/internal/Content.js
+++ b/toolkit/mozapps/extensions/internal/Content.js
@@ -8,9 +8,9 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+var {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
-let nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
+var nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
"initWithPath");
const MSG_JAR_FLUSH = "AddonJarFlush";
diff --git a/toolkit/mozapps/extensions/internal/GMPProvider.jsm b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
index a55457f6e..52affa9ba 100644
--- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
@@ -64,11 +64,11 @@ XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
XPCOMUtils.defineLazyGetter(this, "gmpService",
() => Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(Ci.mozIGeckoMediaPluginChromeService));
-let messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
+var messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
-let gLogger;
-let gLogAppenderDump = null;
+var gLogger;
+var gLogAppenderDump = null;
function configureLogging() {
if (!gLogger) {
@@ -443,7 +443,7 @@ GMPWrapper.prototype = {
},
};
-let GMPProvider = {
+var GMPProvider = {
get name() { return "GMPProvider"; },
_plugins: null,
diff --git a/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm b/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm
index fccde9a81..1e7d6b0d8 100644
--- a/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm
+++ b/toolkit/mozapps/extensions/internal/LightweightThemeImageOptimizer.jsm
@@ -49,7 +49,7 @@ this.LightweightThemeImageOptimizer = {
Object.freeze(LightweightThemeImageOptimizer);
-let ImageCropper = {
+var ImageCropper = {
_inProgress: {},
getCroppedImageURL:
@@ -119,7 +119,7 @@ let ImageCropper = {
}
};
-let ImageFile = {
+var ImageFile = {
read: function ImageFile_read(aURI, aCallback) {
this._netUtil.asyncFetch2(
aURI,
@@ -158,7 +158,7 @@ let ImageFile = {
XPCOMUtils.defineLazyModuleGetter(ImageFile, "_netUtil",
"resource://gre/modules/NetUtil.jsm", "NetUtil");
-let ImageTools = {
+var ImageTools = {
decode: function ImageTools_decode(aInputStream, aContentType) {
let outParam = {value: null};
@@ -187,7 +187,7 @@ let ImageTools = {
XPCOMUtils.defineLazyServiceGetter(ImageTools, "_imgTools",
"@mozilla.org/image/tools;1", "imgITools");
-let Utils = {
+var Utils = {
createCopy: function Utils_createCopy(aData) {
let copy = {};
for (let [k, v] in Iterator(aData)) {
diff --git a/toolkit/mozapps/extensions/internal/PluginProvider.jsm b/toolkit/mozapps/extensions/internal/PluginProvider.jsm
index 04a4f9d7c..cb07dcb12 100644
--- a/toolkit/mozapps/extensions/internal/PluginProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/PluginProvider.jsm
@@ -23,7 +23,7 @@ const LOGGER_ID = "addons.plugins";
// Create a new logger for use by the Addons Plugin Provider
// (Requires AddonManager.jsm)
-let logger = Log.repository.getLogger(LOGGER_ID);
+var logger = Log.repository.getLogger(LOGGER_ID);
function getIDHashForString(aStr) {
// return the two-digit hexadecimal code for a byte
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index 27ecee7d8..2c5e3dfa7 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -222,7 +222,7 @@ const LOGGER_ID = "addons.xpi";
// Create a new logger for use by all objects in this Addons XPI Provider module
// (Requires AddonManager.jsm)
-let logger = Log.repository.getLogger(LOGGER_ID);
+var logger = Log.repository.getLogger(LOGGER_ID);
const LAZY_OBJECTS = ["XPIDatabase"];
@@ -7822,7 +7822,7 @@ WinRegInstallLocation.prototype = {
};
#endif
-let addonTypes = [
+var addonTypes = [
new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 4000,
diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
index 2cef907f1..d26029455 100644
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -29,7 +29,7 @@ const LOGGER_ID = "addons.xpi-utils";
// Create a new logger for use by the Addons XPI Provider Utils
// (Requires AddonManager.jsm)
-let logger = Log.repository.getLogger(LOGGER_ID);
+var logger = Log.repository.getLogger(LOGGER_ID);
const KEY_PROFILEDIR = "ProfD";
const FILE_DATABASE = "extensions.sqlite";
diff --git a/toolkit/mozapps/extensions/test/browser/browser-common.ini b/toolkit/mozapps/extensions/test/browser/browser-common.ini
index eaab29f75..3e88833ef 100644
--- a/toolkit/mozapps/extensions/test/browser/browser-common.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser-common.ini
@@ -39,8 +39,6 @@ skip-if = true # Bug 1093190 - Disabled due to leak
[browser_discovery.js]
skip-if = e10s # Bug ?????? - test times out on try on all platforms, but works locally for markh!
[browser_dragdrop.js]
-skip-if = buildapp == 'mulet'
-[browser_experiments.js]
skip-if = e10s
[browser_list.js]
[browser_metadataTimeout.js]
diff --git a/toolkit/mozapps/webextensions/test/browser/browser-common.ini b/toolkit/mozapps/webextensions/test/browser/browser-common.ini
index eda266e2f..83920465b 100644
--- a/toolkit/mozapps/webextensions/test/browser/browser-common.ini
+++ b/toolkit/mozapps/webextensions/test/browser/browser-common.ini
@@ -34,7 +34,6 @@ skip-if = true # Bug 1093190 - Disabled due to leak
[browser_discovery.js]
[browser_dragdrop.js]
skip-if = buildapp == 'mulet'
-[browser_experiments.js]
[browser_list.js]
[browser_metadataTimeout.js]
[browser_searching.js]
diff --git a/toolkit/themes/shared/datetimeinputpickers.css b/toolkit/themes/shared/datetimeinputpickers.css
new file mode 100644
index 000000000..f0c4315e5
--- /dev/null
+++ b/toolkit/themes/shared/datetimeinputpickers.css
@@ -0,0 +1,377 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+:root {
+ --font-size-default: 1.1rem;
+ --spinner-width: 3rem;
+ --spinner-margin-top-bottom: 0.4rem;
+ --spinner-item-height: 2.4rem;
+ --spinner-item-margin-bottom: 0.1rem;
+ --spinner-button-height: 1.2rem;
+ --colon-width: 2rem;
+ --day-period-spacing-width: 1rem;
+ --calendar-width: 23.1rem;
+ --date-picker-item-height: 2.5rem;
+ --date-picker-item-width: 3.3rem;
+
+ --border: 0.1rem solid #D6D6D6;
+ --border-radius: 0.3rem;
+ --border-active-color: #B1B1B1;
+
+ --font-color: #191919;
+ --fill-color: #EBEBEB;
+
+ --today-fill-color: rgb(212, 212, 212);
+
+ --selected-font-color: #FFFFFF;
+ --selected-fill-color: #0996F8;
+
+ --button-font-color: #858585;
+ --button-font-color-hover: #4D4D4D;
+ --button-font-color-active: #191919;
+ --button-fill-color-active: #D4D4D4;
+
+ --weekday-header-font-color: #6C6C6C;
+ --weekend-header-font-color: rgb(218, 78, 68);
+
+ --weekend-font-color: rgb(218, 78, 68);
+ --weekday-outside-font-color: rgb(153, 153, 153);
+ --weekend-outside-font-color: rgb(255, 152, 143);
+
+ --weekday-disabled-font-color: rgba(25, 25, 25, 0.2);
+ --weekend-disabled-font-color: rgba(218, 78, 68, 0.2);
+ --disabled-fill-color: rgba(235, 235, 235, 0.8);
+
+ --disabled-opacity: 0.2;
+}
+
+html {
+ font-size: 10px;
+}
+
+body {
+ margin: 0;
+ color: var(--font-color);
+ font: message-box;
+ font-size: var(--font-size-default);
+}
+
+button {
+ -moz-appearance: none;
+ background: none;
+ border: none;
+}
+
+.nav {
+ display: flex;
+ width: var(--calendar-width);
+ height: 2.4rem;
+ margin-bottom: 0.8rem;
+ justify-content: space-between;
+}
+
+.nav > button {
+ width: 3rem;
+ height: var(--date-picker-item-height);
+ background-color: var(--button-font-color);
+}
+
+.nav > button:hover {
+ background-color: var(--button-font-color-hover);
+}
+
+.nav > button.active {
+ background-color: var(--button-font-color-active);
+}
+
+.nav > button.left {
+ background: url("chrome://global/skin/icons/calendar-arrows.svg#left") no-repeat 50% 50%;
+}
+
+.nav > button.right {
+ background: url("chrome://global/skin/icons/calendar-arrows.svg#right") no-repeat 50% 50%;
+}
+
+.month-year-container {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ top: 0;
+ left: 3rem;
+ width: 17.1rem;
+ height: var(--date-picker-item-height);
+ z-index: 10;
+}
+
+button.month-year {
+ font-size: 1.3rem;
+ border: var(--border);
+ border-radius: 0.3rem;
+ padding: 0.2rem 2.6rem 0.2rem 1.2rem;
+}
+
+button.month-year:hover {
+ background: var(--fill-color);
+}
+
+button.month-year.active {
+ border-color: var(--border-active-color);
+ background: var(--button-fill-color-active);
+}
+
+button.month-year::after {
+ position: absolute;
+ content: "";
+ width: 2.6rem;
+ height: 1.6rem;
+ background: url("chrome://global/skin/icons/spinner-arrows.svg#down") no-repeat 50% 50%;
+}
+
+button.month-year.active::after {
+ background: url("chrome://global/skin/icons/spinner-arrows.svg#up") no-repeat 50% 50%;
+}
+
+.month-year-view {
+ position: absolute;
+ z-index: 5;
+ padding-top: 3.2rem;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: var(--calendar-width);
+ background: window;
+ opacity: 1;
+ transition: opacity 0.15s;
+}
+
+.month-year-view.hidden {
+ visibility: hidden;
+ opacity: 0;
+}
+
+.month-year-view > .spinner-container {
+ width: 5.5rem;
+ margin: 0 0.5rem;
+}
+
+.month-year-view .spinner {
+ transform: scaleY(1);
+ transform-origin: top;
+ transition: transform 0.15s;
+}
+
+.month-year-view.hidden .spinner {
+ transform: scaleY(0);
+ transition: none;
+}
+
+.month-year-view .spinner > div {
+ transform: scaleY(1);
+ transition: transform 0.15s;
+}
+
+.month-year-view.hidden .spinner > div {
+ transform: scaleY(2.5);
+ transition: none;
+}
+
+.calendar-container {
+ cursor: default;
+ display: flex;
+ flex-direction: column;
+ width: var(--calendar-width);
+}
+
+.week-header {
+ display: flex;
+}
+
+.week-header > div {
+ color: var(--weekday-header-font-color);
+}
+
+.week-header > div.weekend {
+ color: var(--weekend-header-font-color);
+}
+
+.days-viewport {
+ height: 15rem;
+ overflow: hidden;
+ position: relative;
+}
+
+.days-view {
+ position: absolute;
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: row;
+}
+
+.week-header > div,
+.days-view > div {
+ align-items: center;
+ display: flex;
+ height: var(--date-picker-item-height);
+ position: relative;
+ justify-content: center;
+ width: var(--date-picker-item-width);
+}
+
+.days-view > .outside {
+ color: var(--weekday-outside-font-color);
+}
+
+.days-view > .weekend {
+ color: var(--weekend-font-color);
+}
+
+.days-view > .weekend.outside {
+ color: var(--weekend-outside-font-color);
+}
+
+.days-view > .out-of-range,
+.days-view > .off-step {
+ color: var(--weekday-disabled-font-color);
+ background: var(--disabled-fill-color);
+}
+
+.days-view > .out-of-range.weekend,
+.days-view > .off-step.weekend {
+ color: var(--weekend-disabled-font-color);
+}
+
+.days-view > .today {
+ font-weight: bold;
+}
+
+.days-view > .out-of-range::before,
+.days-view > .off-step::before {
+ display: none;
+}
+
+.days-view > div:hover::before,
+.days-view > .select::before,
+.days-view > .today::before {
+ top: 5%;
+ bottom: 5%;
+ left: 5%;
+ right: 5%;
+}
+
+#time-picker,
+.month-year-view {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+}
+
+.spinner-container {
+ display: flex;
+ flex-direction: column;
+ width: var(--spinner-width);
+}
+
+.spinner-container > button {
+ background-color: var(--button-font-color);
+ height: var(--spinner-button-height);
+}
+
+.spinner-container > button:hover {
+ background-color: var(--button-font-color-hover);
+}
+
+.spinner-container > button.active {
+ background-color: var(--button-font-color-active);
+}
+
+.spinner-container > button.up {
+ background: url("chrome://global/skin/icons/spinner-arrows.svg#up") no-repeat 50% 50%;
+}
+
+.spinner-container > button.down {
+ background: url("chrome://global/skin/icons/spinner-arrows.svg#down") no-repeat 50% 50%;
+}
+
+.spinner-container.hide-buttons > button {
+ visibility: hidden;
+}
+
+.spinner-container > .spinner {
+ position: relative;
+ width: 100%;
+ margin: var(--spinner-margin-top-bottom) 0;
+ cursor: default;
+ overflow-y: scroll;
+ scroll-snap-type: mandatory;
+ scroll-snap-points-y: repeat(100%);
+}
+
+.spinner-container > .spinner > div {
+ box-sizing: border-box;
+ position: relative;
+ text-align: center;
+ padding: calc((var(--spinner-item-height) - var(--font-size-default)) / 2) 0;
+ margin-bottom: var(--spinner-item-margin-bottom);
+ height: var(--spinner-item-height);
+ -moz-user-select: none;
+ scroll-snap-coordinate: 0 0;
+}
+
+.spinner-container > .spinner > div::before,
+.calendar-container .days-view > div::before {
+ position: absolute;
+ top: 5%;
+ bottom: 5%;
+ left: 5%;
+ right: 5%;
+ z-index: -10;
+ border-radius: var(--border-radius);
+}
+
+.spinner-container > .spinner > div:hover::before,
+.calendar-container .days-view > div:hover::before {
+ background: var(--fill-color);
+ border: var(--border);
+ content: "";
+}
+
+.calendar-container .days-view > div.today::before {
+ background: var(--today-fill-color);
+ content: "";
+}
+
+.spinner-container > .spinner:not(.scrolling) > div.selection,
+.calendar-container .days-view > div.selection {
+ color: var(--selected-font-color);
+}
+
+.spinner-container > .spinner > div.selection::before,
+.calendar-container .days-view > div.selection::before {
+ background: var(--selected-fill-color);
+ border: none;
+ content: "";
+}
+
+.spinner-container > .spinner > div.disabled::before,
+.spinner-container > .spinner.scrolling > div.selection::before,
+.spinner-container > .spinner.scrolling > div:hover::before {
+ display: none;
+}
+
+.spinner-container > .spinner > div.disabled {
+ opacity: var(--disabled-opacity);
+}
+
+.colon {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: var(--colon-width);
+ margin-bottom: 0.3rem;
+}
+
+.spacer {
+ width: var(--day-period-spacing-width);
+} \ No newline at end of file
diff --git a/toolkit/themes/shared/datetimepopup.css b/toolkit/themes/shared/datetimepopup.css
new file mode 100644
index 000000000..52f6fc7a2
--- /dev/null
+++ b/toolkit/themes/shared/datetimepopup.css
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+panel[type="arrow"][side="top"],
+panel[type="arrow"][side="bottom"] {
+ margin-left: 0;
+ margin-right: 0;
+}
diff --git a/toolkit/themes/shared/icons/calendar-arrows.svg b/toolkit/themes/shared/icons/calendar-arrows.svg
new file mode 100644
index 000000000..858676f55
--- /dev/null
+++ b/toolkit/themes/shared/icons/calendar-arrows.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <style>
+ path:not(:target) {
+ display: none;
+ }
+ </style>
+ <path id="right" d="M4.8 14L3 12.3 8.5 7 3 1.7 4.8 0 12 7"/>
+ <path id="left" d="M9.2 0L11 1.7 5.5 7 11 12.3 9.2 14 2 7"/>
+</svg>
diff --git a/toolkit/themes/shared/icons/spinner-arrows.svg b/toolkit/themes/shared/icons/spinner-arrows.svg
new file mode 100644
index 000000000..a8ba72d6b
--- /dev/null
+++ b/toolkit/themes/shared/icons/spinner-arrows.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="10" height="6" viewBox="0 0 10 6">
+ <style>
+ path:not(:target) {
+ display: none;
+ }
+ </style>
+ <path id="down" d="M0 1l1-1 4 4 4-4 1 1-5 5"/>
+ <path id="up" d="M0 5l1 1 4-4 4 4 1-1-5-5"/>
+</svg>
diff --git a/toolkit/themes/shared/jar.inc.mn b/toolkit/themes/shared/jar.inc.mn
index 9c3d86a40..bdfca2a05 100644
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -21,12 +21,15 @@ toolkit.jar:
skin/classic/global/aboutSupport.css (../../shared/aboutSupport.css)
skin/classic/global/appPicker.css (../../shared/appPicker.css)
skin/classic/global/config.css (../../shared/config.css)
- skin/classic/global/timepicker.css (../../shared/timepicker.css)
+ skin/classic/global/datetimeinputpickers.css (../../shared/datetimeinputpickers.css)
+ skin/classic/global/datetimepopup.css (../../shared/datetimepopup.css)
+ skin/classic/global/icons/calendar-arrows.svg (../../shared/icons/calendar-arrows.svg)
skin/classic/global/icons/find-arrows.svg (../../shared/icons/find-arrows.svg)
skin/classic/global/icons/info.svg (../../shared/incontent-icons/info.svg)
skin/classic/global/icons/input-clear.svg (../../shared/icons/input-clear.svg)
skin/classic/global/icons/loading.png (../../shared/icons/loading.png)
skin/classic/global/icons/loading@2x.png (../../shared/icons/loading@2x.png)
+ skin/classic/global/icons/spinner-arrows.svg (../../shared/icons/spinner-arrows.svg)
skin/classic/global/icons/warning.svg (../../shared/incontent-icons/warning.svg)
skin/classic/global/icons/blocked.svg (../../shared/incontent-icons/blocked.svg)
skin/classic/global/alerts/alert-common.css (../../shared/alert-common.css)
diff --git a/toolkit/themes/shared/timepicker.css b/toolkit/themes/shared/timepicker.css
deleted file mode 100644
index e8d081b30..000000000
--- a/toolkit/themes/shared/timepicker.css
+++ /dev/null
@@ -1,153 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-:root {
- --font-size-default: 1.1rem;
- --spinner-width: 3rem;
- --spinner-margin-top-bottom: 0.4rem;
- --spinner-item-height: 2.4rem;
- --spinner-item-margin-bottom: 0.1rem;
- --spinner-button-height: 1.2rem;
- --colon-width: 2rem;
- --day-period-spacing-width: 1rem;
-
- --border: 0.1rem solid #D6D6D6;
- --border-radius: 0.3rem;
-
- --font-color: #191919;
- --fill-color: #EBEBEB;
-
- --selected-font-color: #FFFFFF;
- --selected-fill-color: #0996F8;
-
- --button-font-color: #858585;
- --button-font-color-hover: #4D4D4D;
- --button-font-color-active: #191919;
-
- --disabled-opacity: 0.2;
-}
-
-html {
- font-size: 10px;
-}
-
-body {
- margin: 0;
- color: var(--font-color);
- font-size: var(--font-size-default);
-}
-
-#time-picker {
- display: flex;
- flex-direction: row;
- justify-content: space-around;
-}
-
-.spinner-container {
- font-family: sans-serif;
- display: flex;
- flex-direction: column;
- width: var(--spinner-width);
-}
-
-.spinner-container > button {
- -moz-appearance: none;
- border: none;
- background: none;
- background-color: var(--button-font-color);
- height: var(--spinner-button-height);
-}
-
-.spinner-container > button:hover {
- background-color: var(--button-font-color-hover);
-}
-
-.spinner-container > button.active {
- background-color: var(--button-font-color-active);
-}
-
-.spinner-container > button.up {
- mask: url("chrome://global/skin/icons/find-arrows.svg#glyph-find-previous") no-repeat 50% 50%;
-}
-
-.spinner-container > button.down {
- mask: url("chrome://global/skin/icons/find-arrows.svg#glyph-find-next") no-repeat 50% 50%;
-}
-
-.spinner-container.hide-buttons > button {
- visibility: hidden;
-}
-
-.spinner-container > .spinner {
- position: relative;
- width: 100%;
- margin: var(--spinner-margin-top-bottom) 0;
- cursor: default;
- overflow-y: scroll;
- scroll-snap-type: mandatory;
- scroll-snap-points-y: repeat(100%);
-}
-
-.spinner-container > .spinner > div {
- box-sizing: border-box;
- position: relative;
- text-align: center;
- padding: calc((var(--spinner-item-height) - var(--font-size-default)) / 2) 0;
- margin-bottom: var(--spinner-item-margin-bottom);
- height: var(--spinner-item-height);
- -moz-user-select: none;
- scroll-snap-coordinate: 0 0;
-}
-
-.spinner-container > .spinner > div:hover::before {
- background: var(--fill-color);
- border: var(--border);
- border-radius: var(--border-radius);
- content: "";
- position: absolute;
- top: 0%;
- bottom: 0%;
- left: 0%;
- right: 0%;
- z-index: -10;
-}
-
-.spinner-container > .spinner:not(.scrolling) > div.selection {
- color: var(--selected-font-color);
-}
-
-.spinner-container > .spinner > div.selection::before {
- background: var(--selected-fill-color);
- border: none;
- border-radius: var(--border-radius);
- content: "";
- position: absolute;
- top: 0%;
- bottom: 0%;
- left: 0%;
- right: 0%;
- z-index: -10;
-}
-
-.spinner-container > .spinner > div.disabled::before,
-.spinner-container > .spinner.scrolling > div.selection::before,
-.spinner-container > .spinner.scrolling > div:hover::before {
- display: none;
-}
-
-.spinner-container > .spinner > div.disabled {
- opacity: var(--disabled-opacity);
-}
-
-.colon {
- display: flex;
- justify-content: center;
- align-items: center;
- width: var(--colon-width);
- margin-bottom: 0.3rem;
-}
-
-.spacer {
- width: var(--day-period-spacing-width);
-} \ No newline at end of file
diff --git a/widget/moz.build b/widget/moz.build
index 09192179f..f69f2d87c 100644
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -65,7 +65,6 @@ XPIDL_SOURCES += [
'nsIClipboardHelper.idl',
'nsIClipboardOwner.idl',
'nsIColorPicker.idl',
- 'nsIDatePicker.idl',
'nsIDisplayInfo.idl',
'nsIDragService.idl',
'nsIDragSession.idl',
@@ -151,7 +150,6 @@ UNIFIED_SOURCES += [
'nsClipboardProxy.cpp',
'nsColorPickerProxy.cpp',
'nsContentProcessWidgetFactory.cpp',
- 'nsDatePickerProxy.cpp',
'nsDragServiceProxy.cpp',
'nsFilePickerProxy.cpp',
'nsHTMLFormatConverter.cpp',
diff --git a/widget/nsContentProcessWidgetFactory.cpp b/widget/nsContentProcessWidgetFactory.cpp
index f8eaee250..2f00f1c83 100644
--- a/widget/nsContentProcessWidgetFactory.cpp
+++ b/widget/nsContentProcessWidgetFactory.cpp
@@ -9,7 +9,6 @@
#include "nsWidgetsCID.h"
#include "nsClipboardProxy.h"
#include "nsColorPickerProxy.h"
-#include "nsDatePickerProxy.h"
#include "nsDragServiceProxy.h"
#include "nsFilePickerProxy.h"
#include "nsScreenManagerProxy.h"
@@ -22,7 +21,6 @@ using namespace mozilla::widget;
NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardProxy)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsColorPickerProxy)
-NS_GENERIC_FACTORY_CONSTRUCTOR(nsDatePickerProxy)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragServiceProxy)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsFilePickerProxy)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerProxy)
@@ -30,7 +28,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(PuppetBidiKeyboard)
NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
-NS_DEFINE_NAMED_CID(NS_DATEPICKER_CID);
NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
NS_DEFINE_NAMED_CID(PUPPETBIDIKEYBOARD_CID);
@@ -41,8 +38,6 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
Module::CONTENT_PROCESS_ONLY },
{ &kNS_COLORPICKER_CID, false, nullptr, nsColorPickerProxyConstructor,
Module::CONTENT_PROCESS_ONLY },
- { &kNS_DATEPICKER_CID, false, nullptr, nsDatePickerProxyConstructor,
- Module::CONTENT_PROCESS_ONLY },
{ &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceProxyConstructor,
Module::CONTENT_PROCESS_ONLY },
{ &kNS_FILEPICKER_CID, false, nullptr, nsFilePickerProxyConstructor,
@@ -57,7 +52,6 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
{ "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID, Module::CONTENT_PROCESS_ONLY },
{ "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID, Module::CONTENT_PROCESS_ONLY },
- { "@mozilla.org/datepicker;1", &kNS_DATEPICKER_CID, Module::CONTENT_PROCESS_ONLY },
{ "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID, Module::CONTENT_PROCESS_ONLY },
{ "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID, Module::CONTENT_PROCESS_ONLY },
{ "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::CONTENT_PROCESS_ONLY },
diff --git a/widget/nsDatePickerProxy.cpp b/widget/nsDatePickerProxy.cpp
deleted file mode 100644
index e6b88f1be..000000000
--- a/widget/nsDatePickerProxy.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsDatePickerProxy.h"
-
-#include "mozilla/dom/TabChild.h"
-
-using namespace mozilla::dom;
-
-NS_IMPL_ISUPPORTS(nsDatePickerProxy, nsIDatePicker)
-
-/* void init (in nsIDOMWindow parent, in AString title, in short mode); */
-NS_IMETHODIMP
-nsDatePickerProxy::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
- const nsAString& aInitialDate)
-{
- TabChild* tabChild = TabChild::GetFrom(aParent);
- if (!tabChild) {
- return NS_ERROR_FAILURE;
- }
-
- tabChild->SendPDatePickerConstructor(this,
- nsString(aTitle),
- nsString(aInitialDate));
- NS_ADDREF_THIS(); //Released in DeallocPDatePickerChild
- return NS_OK;
-}
-
-/* void open (in nsIDatePickerShownCallback aDatePickerShownCallback); */
-NS_IMETHODIMP
-nsDatePickerProxy::Open(nsIDatePickerShownCallback* aDatePickerShownCallback)
-{
- NS_ENSURE_STATE(!mCallback);
- mCallback = aDatePickerShownCallback;
-
- SendOpen();
- return NS_OK;
-}
-
-bool
-nsDatePickerProxy::RecvCancel()
-{
- if (mCallback) {
- mCallback->Cancel();
- mCallback = nullptr;
- }
- return true;
-}
-
-bool
-nsDatePickerProxy::Recv__delete__(const nsString& aDate)
-{
- if (mCallback) {
- mCallback->Done(aDate);
- mCallback = nullptr;
- }
- return true;
-}
diff --git a/widget/nsDatePickerProxy.h b/widget/nsDatePickerProxy.h
deleted file mode 100644
index 71475932c..000000000
--- a/widget/nsDatePickerProxy.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef nsDatePickerProxy_h
-#define nsDatePickerProxy_h
-
-#include "nsIDatePicker.h"
-
-#include "mozilla/dom/PDatePickerChild.h"
-
-class nsDatePickerProxy final : public nsIDatePicker,
- public mozilla::dom::PDatePickerChild
-{
-public:
- NS_DECL_ISUPPORTS
- NS_DECL_NSIDATEPICKER
-
- nsDatePickerProxy() {}
-
- virtual bool RecvCancel() override;
- virtual bool Recv__delete__(const nsString& aDate) override;
-
-private:
- ~nsDatePickerProxy() {}
-
- nsCOMPtr<nsIDatePickerShownCallback> mCallback;
- nsString mTitle;
- nsString mInitialDate;
-};
-
-#endif // nsDatePickerProxy_h
diff --git a/widget/nsIDatePicker.idl b/widget/nsIDatePicker.idl
deleted file mode 100644
index d6be60c95..000000000
--- a/widget/nsIDatePicker.idl
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-interface mozIDOMWindowProxy;
-
-[scriptable, uuid(13388a28-1b0b-4218-a31b-588f7a4ec26c)]
-interface nsIDatePickerShownCallback : nsISupports
-{
- /**
- * Callback called when the user selects cancel in the date picker
- * This callback can not be called after done() is called.
- */
- void cancel();
-
- /**
- * Callback called when the user has finished selecting the date
- *
- * @param date The new selected date value following the format "YYYY-MM-DD"
- */
- void done(in AString date);
-};
-
-[scriptable, uuid(7becfc64-966b-4d53-87d2-9161f36bd3b3)]
-interface nsIDatePicker : nsISupports
-{
- /**
- * Initialize the date picker widget. The date picker will not be shown until
- * open() is called.
- * If the initialDate parameter does not follow the format "YYYY-MM-DD" then
- * the behavior will be unspecified.
- *
- * @param parent nsIDOMWindow parent. This dialog will be dependent
- * on this parent. parent may be null.
- * @param title The title for the date picker widget.
- * @param initialDate The date to show when the widget is opened. The
- * parameter has to follow the format "YYYY-MM-DD"
- */
- void init(in mozIDOMWindowProxy parent, in AString title, in AString initialDate);
-
- /**
- * Opens the date dialog asynchrounously.
- * The results are provided via the callback object.
- */
- void open(in nsIDatePickerShownCallback callback);
-};
diff --git a/widget/nsWidgetsCID.h b/widget/nsWidgetsCID.h
index 2589b59e0..54ebe63ab 100644
--- a/widget/nsWidgetsCID.h
+++ b/widget/nsWidgetsCID.h
@@ -33,11 +33,6 @@
{ 0x0f872c8c, 0x3ee6, 0x46bd, \
{ 0x92, 0xa2, 0x69, 0x65, 0x2c, 0x6b, 0x47, 0x4e } }
-/* 0ca832f8-978a-4dc7-a57d-adb803925d39 */
-#define NS_DATEPICKER_CID \
-{ 0x0ca832f8, 0x978a, 0x4dc7, \
- { 0xa5, 0x7d, 0xad, 0xb8, 0x03, 0x92, 0x5d, 0x39 } }
-
/* 2d96b3df-c051-11d1-a827-0040959a28c9 */
#define NS_APPSHELL_CID \
{ 0x2d96b3df, 0xc051, 0x11d1, \
diff --git a/xpcom/io/nsEscape.cpp b/xpcom/io/nsEscape.cpp
index f16edc4ce..117e20d60 100644
--- a/xpcom/io/nsEscape.cpp
+++ b/xpcom/io/nsEscape.cpp
@@ -346,7 +346,7 @@ static const uint32_t EscapeChars[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
- 0,1023, 0, 512,1023, 0,1023, 112,1023,1023,1023,1023,1023,1023, 953, 784, // 2x !"#$%&'()*+,-./
+ 0,1023, 0, 512,1023, 0,1023, 624,1023,1023,1023,1023,1023,1023, 953, 784, // 2x !"#$%&'()*+,-./
1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1008,1008, 0,1008, 0, 768, // 3x 0123456789:;<=>?
1008,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, // 4x @ABCDEFGHIJKLMNO
1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1008, 896,1008, 896,1023, // 5x PQRSTUVWXYZ[\]^_