mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-16 06:45:42 +00:00
Merge m-c to autoland. a=merge
This commit is contained in:
commit
35753f09fc
@ -102,7 +102,7 @@ devtools/client/shared/test/**
|
||||
devtools/client/shared/widgets/*.jsm
|
||||
devtools/client/sourceeditor/test/*.js
|
||||
devtools/client/webaudioeditor/**
|
||||
#devtools/client/webconsole/**
|
||||
devtools/client/webconsole/**
|
||||
!devtools/client/webconsole/panel.js
|
||||
!devtools/client/webconsole/jsterm.js
|
||||
!devtools/client/webconsole/console-commands.js
|
||||
|
@ -154,6 +154,12 @@
|
||||
flip="none"
|
||||
level="parent"/>
|
||||
|
||||
<panel id="DateTimePickerPanel"
|
||||
hidden="true"
|
||||
noautofocus="true"
|
||||
consumeoutsideclicks="false"
|
||||
level="parent"/>
|
||||
|
||||
<!-- for select dropdowns. The menupopup is what shows the list of options,
|
||||
and the popuponly menulist makes things like the menuactive attributes
|
||||
work correctly on the menupopup. ContentSelectDropdown expects the
|
||||
@ -1056,7 +1062,8 @@
|
||||
tabcontainer="tabbrowser-tabs"
|
||||
contentcontextmenu="contentAreaContextMenu"
|
||||
autocompletepopup="PopupAutoComplete"
|
||||
selectmenulist="ContentSelectDropdown"/>
|
||||
selectmenulist="ContentSelectDropdown"
|
||||
datetimepicker="DateTimePickerPanel"/>
|
||||
</vbox>
|
||||
<vbox id="browser-border-end" hidden="true" layer="true"/>
|
||||
</hbox>
|
||||
|
@ -25,7 +25,7 @@
|
||||
<xul:vbox flex="1" class="browserContainer">
|
||||
<xul:stack flex="1" class="browserStack" anonid="browserStack">
|
||||
<xul:browser anonid="initialBrowser" type="content-primary" message="true" messagemanagergroup="browsers"
|
||||
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist"/>
|
||||
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist,datetimepicker"/>
|
||||
</xul:stack>
|
||||
</xul:vbox>
|
||||
</xul:hbox>
|
||||
@ -1886,6 +1886,10 @@
|
||||
if (this.hasAttribute("selectmenulist"))
|
||||
b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
|
||||
|
||||
if (this.hasAttribute("datetimepicker")) {
|
||||
b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
|
||||
}
|
||||
|
||||
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
|
||||
|
||||
if (aParams.relatedBrowser) {
|
||||
|
@ -33,6 +33,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s
|
||||
["ContentClick", "resource:///modules/ContentClick.jsm"],
|
||||
["ContentPrefServiceParent", "resource://gre/modules/ContentPrefServiceParent.jsm"],
|
||||
["ContentSearch", "resource:///modules/ContentSearch.jsm"],
|
||||
["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"],
|
||||
["DirectoryLinksProvider", "resource:///modules/DirectoryLinksProvider.jsm"],
|
||||
["Feeds", "resource:///modules/Feeds.jsm"],
|
||||
["FileUtils", "resource://gre/modules/FileUtils.jsm"],
|
||||
@ -1022,6 +1023,7 @@ BrowserGlue.prototype = {
|
||||
CaptivePortalWatcher.init();
|
||||
|
||||
AutoCompletePopup.init();
|
||||
DateTimePickerHelper.init();
|
||||
|
||||
this._firstWindowTelemetry(aWindow);
|
||||
this._firstWindowLoaded();
|
||||
@ -1053,6 +1055,7 @@ BrowserGlue.prototype = {
|
||||
webrtcUI.uninit();
|
||||
FormValidationHandler.uninit();
|
||||
AutoCompletePopup.uninit();
|
||||
DateTimePickerHelper.uninit();
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
AddonWatcher.uninit();
|
||||
}
|
||||
|
@ -29,3 +29,4 @@ support-files =
|
||||
[browser_blobURLIsolation.js]
|
||||
[browser_imageCacheIsolation.js]
|
||||
[browser_sharedworker.js]
|
||||
[browser_httpauth.js]
|
||||
|
@ -0,0 +1,54 @@
|
||||
let Cu = Components.utils;
|
||||
let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
|
||||
|
||||
let server = new HttpServer();
|
||||
server.registerPathHandler('/file.html', fileHandler);
|
||||
server.start(-1);
|
||||
|
||||
let BASE_URI = 'http://localhost:' + server.identity.primaryPort;
|
||||
let FILE_URI = BASE_URI + '/file.html';
|
||||
|
||||
let credentialQueue = [];
|
||||
|
||||
// Ask the user agent for authorization.
|
||||
function fileHandler(metadata, response) {
|
||||
if (!metadata.hasHeader("Authorization")) {
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
response.setHeader("WWW-Authenticate", "Basic realm=\"User Visible Realm\"");
|
||||
return;
|
||||
}
|
||||
|
||||
// This will be "account:password" encoded in base64.
|
||||
credentialQueue.push(metadata.getHeader("Authorization"));
|
||||
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
let body = "<html><body></body></html>";
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function onCommonDialogLoaded(subject) {
|
||||
// Submit random account and password
|
||||
let dialog = subject.Dialog;
|
||||
dialog.ui.loginTextbox.setAttribute("value", Math.random());
|
||||
dialog.ui.password1Textbox.setAttribute("value", Math.random());
|
||||
dialog.ui.button0.click();
|
||||
}
|
||||
|
||||
Services.obs.addObserver(onCommonDialogLoaded, "common-dialog-loaded", false);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.obs.removeObserver(onCommonDialogLoaded, "common-dialog-loaded");
|
||||
server.stop(() => {
|
||||
server = null;
|
||||
});
|
||||
});
|
||||
|
||||
function getResult() {
|
||||
// If two targets are isolated, they should get different credentials.
|
||||
// Otherwise, the credentials will be cached and therefore the same.
|
||||
return credentialQueue.shift();
|
||||
}
|
||||
|
||||
IsolationTestTools.runTests(FILE_URI, getResult);
|
||||
|
@ -1,3 +1,3 @@
|
||||
This is the pdf.js project output, https://github.com/mozilla/pdf.js
|
||||
|
||||
Current extension version is: 1.5.498
|
||||
Current extension version is: 1.6.221
|
||||
|
@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdf = {}));
|
||||
// Use strict in our context only - users might not want it
|
||||
'use strict';
|
||||
|
||||
var pdfjsVersion = '1.5.498';
|
||||
var pdfjsBuild = '1564dc3';
|
||||
var pdfjsVersion = '1.6.221';
|
||||
var pdfjsBuild = 'f8bd3d4';
|
||||
|
||||
var pdfjsFilePath =
|
||||
typeof document !== 'undefined' && document.currentScript ?
|
||||
@ -851,15 +851,15 @@ var Util = (function UtilClosure() {
|
||||
}
|
||||
};
|
||||
|
||||
Util.getInheritableProperty = function Util_getInheritableProperty(dict,
|
||||
name) {
|
||||
Util.getInheritableProperty =
|
||||
function Util_getInheritableProperty(dict, name, getArray) {
|
||||
while (dict && !dict.has(name)) {
|
||||
dict = dict.get('Parent');
|
||||
}
|
||||
if (!dict) {
|
||||
return null;
|
||||
}
|
||||
return dict.get(name);
|
||||
return getArray ? dict.getArray(name) : dict.get(name);
|
||||
};
|
||||
|
||||
Util.inherit = function Util_inherit(sub, base, prototype) {
|
||||
@ -1993,6 +1993,8 @@ AnnotationElementFactory.prototype =
|
||||
switch (fieldType) {
|
||||
case 'Tx':
|
||||
return new TextWidgetAnnotationElement(parameters);
|
||||
case 'Ch':
|
||||
return new ChoiceWidgetAnnotationElement(parameters);
|
||||
}
|
||||
return new WidgetAnnotationElement(parameters);
|
||||
|
||||
@ -2317,9 +2319,7 @@ var TextAnnotationElement = (function TextAnnotationElementClosure() {
|
||||
* @alias WidgetAnnotationElement
|
||||
*/
|
||||
var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() {
|
||||
function WidgetAnnotationElement(parameters) {
|
||||
var isRenderable = parameters.renderInteractiveForms ||
|
||||
(!parameters.data.hasAppearance && !!parameters.data.fieldValue);
|
||||
function WidgetAnnotationElement(parameters, isRenderable) {
|
||||
AnnotationElement.call(this, parameters, isRenderable);
|
||||
}
|
||||
|
||||
@ -2349,7 +2349,9 @@ var TextWidgetAnnotationElement = (
|
||||
var TEXT_ALIGNMENT = ['left', 'center', 'right'];
|
||||
|
||||
function TextWidgetAnnotationElement(parameters) {
|
||||
WidgetAnnotationElement.call(this, parameters);
|
||||
var isRenderable = parameters.renderInteractiveForms ||
|
||||
(!parameters.data.hasAppearance && !!parameters.data.fieldValue);
|
||||
WidgetAnnotationElement.call(this, parameters, isRenderable);
|
||||
}
|
||||
|
||||
Util.inherit(TextWidgetAnnotationElement, WidgetAnnotationElement, {
|
||||
@ -2445,6 +2447,64 @@ var TextWidgetAnnotationElement = (
|
||||
return TextWidgetAnnotationElement;
|
||||
})();
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @alias ChoiceWidgetAnnotationElement
|
||||
*/
|
||||
var ChoiceWidgetAnnotationElement = (
|
||||
function ChoiceWidgetAnnotationElementClosure() {
|
||||
function ChoiceWidgetAnnotationElement(parameters) {
|
||||
WidgetAnnotationElement.call(this, parameters,
|
||||
parameters.renderInteractiveForms);
|
||||
}
|
||||
|
||||
Util.inherit(ChoiceWidgetAnnotationElement, WidgetAnnotationElement, {
|
||||
/**
|
||||
* Render the choice widget annotation's HTML element in the empty
|
||||
* container.
|
||||
*
|
||||
* @public
|
||||
* @memberof ChoiceWidgetAnnotationElement
|
||||
* @returns {HTMLSectionElement}
|
||||
*/
|
||||
render: function ChoiceWidgetAnnotationElement_render() {
|
||||
this.container.className = 'choiceWidgetAnnotation';
|
||||
|
||||
var selectElement = document.createElement('select');
|
||||
selectElement.disabled = this.data.readOnly;
|
||||
|
||||
if (!this.data.combo) {
|
||||
// List boxes have a size and (optionally) multiple selection.
|
||||
selectElement.size = this.data.options.length;
|
||||
|
||||
if (this.data.multiSelect) {
|
||||
selectElement.multiple = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the options into the choice field.
|
||||
for (var i = 0, ii = this.data.options.length; i < ii; i++) {
|
||||
var option = this.data.options[i];
|
||||
|
||||
var optionElement = document.createElement('option');
|
||||
optionElement.textContent = option.displayValue;
|
||||
optionElement.value = option.exportValue;
|
||||
|
||||
if (this.data.fieldValue.indexOf(option.displayValue) >= 0) {
|
||||
optionElement.setAttribute('selected', true);
|
||||
}
|
||||
|
||||
selectElement.appendChild(optionElement);
|
||||
}
|
||||
|
||||
this.container.appendChild(selectElement);
|
||||
return this.container;
|
||||
}
|
||||
});
|
||||
|
||||
return ChoiceWidgetAnnotationElement;
|
||||
})();
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @alias PopupAnnotationElement
|
||||
|
@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdfWorker = {}));
|
||||
// Use strict in our context only - users might not want it
|
||||
'use strict';
|
||||
|
||||
var pdfjsVersion = '1.5.498';
|
||||
var pdfjsBuild = '1564dc3';
|
||||
var pdfjsVersion = '1.6.221';
|
||||
var pdfjsBuild = 'f8bd3d4';
|
||||
|
||||
var pdfjsFilePath =
|
||||
typeof document !== 'undefined' && document.currentScript ?
|
||||
@ -1837,15 +1837,15 @@ var Util = (function UtilClosure() {
|
||||
}
|
||||
};
|
||||
|
||||
Util.getInheritableProperty = function Util_getInheritableProperty(dict,
|
||||
name) {
|
||||
Util.getInheritableProperty =
|
||||
function Util_getInheritableProperty(dict, name, getArray) {
|
||||
while (dict && !dict.has(name)) {
|
||||
dict = dict.get('Parent');
|
||||
}
|
||||
if (!dict) {
|
||||
return null;
|
||||
}
|
||||
return dict.get(name);
|
||||
return getArray ? dict.getArray(name) : dict.get(name);
|
||||
};
|
||||
|
||||
Util.inherit = function Util_inherit(sub, base, prototype) {
|
||||
@ -24660,7 +24660,10 @@ var Parser = (function ParserClosure() {
|
||||
return stream;
|
||||
},
|
||||
makeFilter: function Parser_makeFilter(stream, name, maybeLength, params) {
|
||||
if (stream.dict.get('Length') === 0 && !maybeLength) {
|
||||
// Since the 'Length' entry in the stream dictionary can be completely
|
||||
// wrong, e.g. zero for non-empty streams, only skip parsing the stream
|
||||
// when we can be absolutely certain that it actually is empty.
|
||||
if (maybeLength === 0) {
|
||||
warn('Empty "' + name + '" stream.');
|
||||
return new NullStream(stream);
|
||||
}
|
||||
@ -39368,6 +39371,8 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
|
||||
switch (fieldType) {
|
||||
case 'Tx':
|
||||
return new TextWidgetAnnotation(parameters);
|
||||
case 'Ch':
|
||||
return new ChoiceWidgetAnnotation(parameters);
|
||||
}
|
||||
warn('Unimplemented widget field type "' + fieldType + '", ' +
|
||||
'falling back to base field type.');
|
||||
@ -39881,8 +39886,8 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() {
|
||||
var data = this.data;
|
||||
|
||||
data.annotationType = AnnotationType.WIDGET;
|
||||
data.fieldValue = stringToPDFString(
|
||||
Util.getInheritableProperty(dict, 'V') || '');
|
||||
data.fieldValue = Util.getInheritableProperty(dict, 'V',
|
||||
/* getArray = */ true);
|
||||
data.alternativeText = stringToPDFString(dict.get('TU') || '');
|
||||
data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
|
||||
var fieldType = Util.getInheritableProperty(dict, 'FT');
|
||||
@ -39894,6 +39899,8 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() {
|
||||
data.fieldFlags = 0;
|
||||
}
|
||||
|
||||
data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
|
||||
|
||||
// Hide signatures because we cannot validate them.
|
||||
if (data.fieldType === 'Sig') {
|
||||
this.setFlags(AnnotationFlag.HIDDEN);
|
||||
@ -39955,6 +39962,9 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
|
||||
function TextWidgetAnnotation(params) {
|
||||
WidgetAnnotation.call(this, params);
|
||||
|
||||
// The field value is always a string.
|
||||
this.data.fieldValue = stringToPDFString(this.data.fieldValue || '');
|
||||
|
||||
// Determine the alignment of text in the field.
|
||||
var alignment = Util.getInheritableProperty(params.dict, 'Q');
|
||||
if (!isInt(alignment) || alignment < 0 || alignment > 2) {
|
||||
@ -39970,7 +39980,6 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
|
||||
this.data.maxLen = maximumLength;
|
||||
|
||||
// Process field flags for the display layer.
|
||||
this.data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
|
||||
this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
|
||||
this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) &&
|
||||
!this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) &&
|
||||
@ -40014,6 +40023,62 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
|
||||
return TextWidgetAnnotation;
|
||||
})();
|
||||
|
||||
var ChoiceWidgetAnnotation = (function ChoiceWidgetAnnotationClosure() {
|
||||
function ChoiceWidgetAnnotation(params) {
|
||||
WidgetAnnotation.call(this, params);
|
||||
|
||||
// Determine the options. The options array may consist of strings or
|
||||
// arrays. If the array consists of arrays, then the first element of
|
||||
// each array is the export value and the second element of each array is
|
||||
// the display value. If the array consists of strings, then these
|
||||
// represent both the export and display value. In this case, we convert
|
||||
// it to an array of arrays as well for convenience in the display layer.
|
||||
this.data.options = [];
|
||||
|
||||
var options = params.dict.getArray('Opt');
|
||||
if (isArray(options)) {
|
||||
for (var i = 0, ii = options.length; i < ii; i++) {
|
||||
var option = options[i];
|
||||
|
||||
this.data.options[i] = {
|
||||
exportValue: isArray(option) ? option[0] : option,
|
||||
displayValue: isArray(option) ? option[1] : option,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the field value. In this case, it may be a string or an
|
||||
// array of strings. For convenience in the display layer, convert the
|
||||
// string to an array of one string as well.
|
||||
if (!isArray(this.data.fieldValue)) {
|
||||
this.data.fieldValue = [this.data.fieldValue];
|
||||
}
|
||||
|
||||
// Process field flags for the display layer.
|
||||
this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
|
||||
this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
|
||||
}
|
||||
|
||||
Util.inherit(ChoiceWidgetAnnotation, WidgetAnnotation, {
|
||||
getOperatorList:
|
||||
function ChoiceWidgetAnnotation_getOperatorList(evaluator, task,
|
||||
renderForms) {
|
||||
var operatorList = new OperatorList();
|
||||
|
||||
// Do not render form elements on the canvas when interactive forms are
|
||||
// enabled. The display layer is responsible for rendering them instead.
|
||||
if (renderForms) {
|
||||
return Promise.resolve(operatorList);
|
||||
}
|
||||
|
||||
return Annotation.prototype.getOperatorList.call(this, evaluator, task,
|
||||
renderForms);
|
||||
}
|
||||
});
|
||||
|
||||
return ChoiceWidgetAnnotation;
|
||||
})();
|
||||
|
||||
var TextAnnotation = (function TextAnnotationClosure() {
|
||||
var DEFAULT_ICON_SIZE = 22; // px
|
||||
|
||||
|
@ -102,7 +102,8 @@
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input,
|
||||
.annotationLayer .textWidgetAnnotation textarea {
|
||||
.annotationLayer .textWidgetAnnotation textarea,
|
||||
.annotationLayer .choiceWidgetAnnotation select {
|
||||
background-color: rgba(0, 54, 255, 0.13);
|
||||
border: 1px solid transparent;
|
||||
box-sizing: border-box;
|
||||
@ -120,19 +121,22 @@
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input[disabled],
|
||||
.annotationLayer .textWidgetAnnotation textarea[disabled] {
|
||||
.annotationLayer .textWidgetAnnotation textarea[disabled],
|
||||
.annotationLayer .choiceWidgetAnnotation select[disabled] {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input:hover,
|
||||
.annotationLayer .textWidgetAnnotation textarea:hover {
|
||||
.annotationLayer .textWidgetAnnotation textarea:hover,
|
||||
.annotationLayer .choiceWidgetAnnotation select:hover {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input:focus,
|
||||
.annotationLayer .textWidgetAnnotation textarea:focus {
|
||||
.annotationLayer .textWidgetAnnotation textarea:focus,
|
||||
.annotationLayer .choiceWidgetAnnotation select:focus {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
@ -1936,8 +1940,11 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: visible;
|
||||
page-break-after: always;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
#printContainer canvas {
|
||||
display: block;
|
||||
|
@ -5668,22 +5668,18 @@ var PDFPageView = (function PDFPageViewClosure() {
|
||||
var pdfPage = this.pdfPage;
|
||||
|
||||
var viewport = pdfPage.getViewport(1);
|
||||
// Use the same hack we use for high dpi displays for printing to get
|
||||
// better output until bug 811002 is fixed in FF.
|
||||
var PRINT_OUTPUT_SCALE = 2;
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
|
||||
// The logical size of the canvas.
|
||||
canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
|
||||
canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
|
||||
// The size of the canvas in pixels for printing.
|
||||
var PRINT_RESOLUTION = 150;
|
||||
var PRINT_UNITS = PRINT_RESOLUTION / 72.0;
|
||||
canvas.width = Math.floor(viewport.width * PRINT_UNITS);
|
||||
canvas.height = Math.floor(viewport.height * PRINT_UNITS);
|
||||
|
||||
// The rendered size of the canvas, relative to the size of canvasWrapper.
|
||||
canvas.style.width = (PRINT_OUTPUT_SCALE * 100) + '%';
|
||||
|
||||
var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
|
||||
(1 / PRINT_OUTPUT_SCALE) + ')';
|
||||
CustomStyle.setProp('transform' , canvas, cssScale);
|
||||
CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
|
||||
// The physical size of the canvas as specified by the PDF document.
|
||||
canvas.style.width = Math.floor(viewport.width * CSS_UNITS) + 'px';
|
||||
canvas.style.height = Math.floor(viewport.height * CSS_UNITS) + 'px';
|
||||
|
||||
var canvasWrapper = document.createElement('div');
|
||||
canvasWrapper.appendChild(canvas);
|
||||
@ -5696,10 +5692,10 @@ var PDFPageView = (function PDFPageViewClosure() {
|
||||
ctx.fillStyle = 'rgb(255, 255, 255)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.restore();
|
||||
ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
|
||||
|
||||
var renderContext = {
|
||||
canvasContext: ctx,
|
||||
transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0],
|
||||
viewport: viewport,
|
||||
intent: 'print'
|
||||
};
|
||||
|
@ -96,6 +96,7 @@ registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.dump.emit");
|
||||
Services.prefs.clearUserPref("devtools.toolbox.host");
|
||||
Services.prefs.clearUserPref("devtools.toolbox.previousHost");
|
||||
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
|
||||
});
|
||||
|
||||
registerCleanupFunction(function* cleanup() {
|
||||
|
@ -314,8 +314,12 @@ pref("devtools.webconsole.timestampMessages", false);
|
||||
// to automatically trigger multiline editing (equivalent to shift + enter).
|
||||
pref("devtools.webconsole.autoMultiline", true);
|
||||
|
||||
// Enable the experimental webconsole frontend (work in progress)
|
||||
// Enable the experimental webconsole frontend
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
pref("devtools.webconsole.new-frontend-enabled", true);
|
||||
#else
|
||||
pref("devtools.webconsole.new-frontend-enabled", false);
|
||||
#endif
|
||||
|
||||
// Enable the experimental support for source maps in console (work in progress)
|
||||
pref("devtools.sourcemap.locations.enabled", false);
|
||||
|
@ -38,7 +38,6 @@ define(function (require, exports, module) {
|
||||
delim = (i == array.length - 1 ? "" : ", ");
|
||||
|
||||
items.push(ItemRep({
|
||||
key: i,
|
||||
object: value,
|
||||
// Hardcode tiny mode to avoid recursive handling.
|
||||
mode: "tiny",
|
||||
@ -46,7 +45,6 @@ define(function (require, exports, module) {
|
||||
}));
|
||||
} catch (exc) {
|
||||
items.push(ItemRep({
|
||||
key: i,
|
||||
object: exc,
|
||||
mode: "tiny",
|
||||
delim: delim
|
||||
@ -57,7 +55,6 @@ define(function (require, exports, module) {
|
||||
if (array.length > max) {
|
||||
let objectLink = this.props.objectLink || DOM.span;
|
||||
items.push(Caption({
|
||||
key: "more",
|
||||
object: objectLink({
|
||||
object: this.props.object
|
||||
}, (array.length - max) + " more…")
|
||||
@ -124,7 +121,7 @@ define(function (require, exports, module) {
|
||||
|
||||
if (mode == "tiny") {
|
||||
let isEmpty = object.length === 0;
|
||||
items = DOM.span({className: "length"}, isEmpty ? "" : object.length);
|
||||
items = [DOM.span({className: "length"}, isEmpty ? "" : object.length)];
|
||||
brackets = needSpace(false);
|
||||
} else {
|
||||
let max = (mode == "short") ? 3 : 300;
|
||||
@ -141,7 +138,7 @@ define(function (require, exports, module) {
|
||||
className: "arrayLeftBracket",
|
||||
object: object
|
||||
}, brackets.left),
|
||||
items,
|
||||
...items,
|
||||
objectLink({
|
||||
className: "arrayRightBracket",
|
||||
object: object
|
||||
|
@ -70,16 +70,14 @@ define(function (require, exports, module) {
|
||||
delim = (i == delimMax ? "" : ", ");
|
||||
|
||||
items.push(GripArrayItem(Object.assign({}, this.props, {
|
||||
key: i,
|
||||
object: value,
|
||||
delim: delim}
|
||||
)));
|
||||
delim: delim
|
||||
})));
|
||||
} catch (exc) {
|
||||
items.push(GripArrayItem(Object.assign({}, this.props, {
|
||||
object: exc,
|
||||
delim: delim,
|
||||
key: i}
|
||||
)));
|
||||
delim: delim
|
||||
})));
|
||||
}
|
||||
}
|
||||
if (array.length > max || grip.preview.length > array.length) {
|
||||
@ -87,7 +85,6 @@ define(function (require, exports, module) {
|
||||
let leftItemNum = grip.preview.length - max > 0 ?
|
||||
grip.preview.length - max : grip.preview.length - array.length;
|
||||
items.push(Caption({
|
||||
key: "more",
|
||||
object: objectLink({
|
||||
object: this.props.object
|
||||
}, leftItemNum + " more…")
|
||||
@ -110,7 +107,7 @@ define(function (require, exports, module) {
|
||||
if (mode == "tiny") {
|
||||
let objectLength = this.getLength(object);
|
||||
let isEmpty = objectLength === 0;
|
||||
items = span({className: "length"}, isEmpty ? "" : objectLength);
|
||||
items = [span({className: "length"}, isEmpty ? "" : objectLength)];
|
||||
brackets = needSpace(false);
|
||||
} else {
|
||||
let max = (mode == "short") ? 3 : 300;
|
||||
@ -129,7 +126,7 @@ define(function (require, exports, module) {
|
||||
className: "arrayLeftBracket",
|
||||
object: object
|
||||
}, brackets.left),
|
||||
items,
|
||||
...items,
|
||||
objectLink({
|
||||
className: "arrayRightBracket",
|
||||
object: object
|
||||
|
@ -76,7 +76,6 @@ define(function (require, exports, module) {
|
||||
let objectLink = this.props.objectLink || span;
|
||||
|
||||
props.push(Caption({
|
||||
key: "more",
|
||||
object: objectLink({
|
||||
object: object
|
||||
}, ((object ? object.ownPropertyLength : 0) - max) + " more…")
|
||||
@ -114,7 +113,6 @@ define(function (require, exports, module) {
|
||||
let prop = ownProperties[name];
|
||||
let value = prop.value !== undefined ? prop.value : prop;
|
||||
props.push(PropRep(Object.assign({}, this.props, {
|
||||
key: name,
|
||||
mode: "tiny",
|
||||
name: name,
|
||||
object: value,
|
||||
@ -190,7 +188,7 @@ define(function (require, exports, module) {
|
||||
className: "objectLeftBrace",
|
||||
object: object
|
||||
}, " { "),
|
||||
props,
|
||||
...props,
|
||||
objectLink({
|
||||
className: "objectRightBrace",
|
||||
object: object
|
||||
|
@ -75,7 +75,6 @@ define(function (require, exports, module) {
|
||||
let objectLink = this.props.objectLink || span;
|
||||
|
||||
props.push(Caption({
|
||||
key: "more",
|
||||
object: objectLink({
|
||||
object: object
|
||||
}, (Object.keys(object).length - max) + " more…")
|
||||
@ -116,7 +115,6 @@ define(function (require, exports, module) {
|
||||
let t = typeof value;
|
||||
if (filter(t, value)) {
|
||||
props.push(PropRep({
|
||||
key: name,
|
||||
mode: mode,
|
||||
name: name,
|
||||
object: value,
|
||||
@ -152,7 +150,7 @@ define(function (require, exports, module) {
|
||||
className: "objectLeftBrace",
|
||||
object: object
|
||||
}, " { "),
|
||||
props,
|
||||
...props,
|
||||
objectLink({
|
||||
className: "objectRightBrace",
|
||||
object: object
|
||||
|
@ -31,6 +31,9 @@ window.onload = Task.async(function* () {
|
||||
// Test that properties are rendered as expected by PropRep
|
||||
yield testNestedObject();
|
||||
yield testNestedArray();
|
||||
|
||||
// Test that 'more' property doesn't clobber the caption.
|
||||
yield testMoreProp();
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
@ -199,6 +202,35 @@ window.onload = Task.async(function* () {
|
||||
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
|
||||
}
|
||||
|
||||
function testMoreProp() {
|
||||
// Test object: `{a: undefined, b: 1, more: 2, d: 3}`;
|
||||
const testName = "testMoreProp";
|
||||
|
||||
const defaultOutput = `Object { b: 1, more: 2, d: 3, 1 more… }`;
|
||||
const longOutput = `Object { a: undefined, b: 1, more: 2, d: 3 }`;
|
||||
|
||||
const modeTests = [
|
||||
{
|
||||
mode: undefined,
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "tiny",
|
||||
expectedOutput: `Object`,
|
||||
},
|
||||
{
|
||||
mode: "short",
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "long",
|
||||
expectedOutput: longOutput,
|
||||
}
|
||||
];
|
||||
|
||||
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
|
||||
}
|
||||
|
||||
function getGripStub(functionName) {
|
||||
switch (functionName) {
|
||||
case "testBasic":
|
||||
@ -405,6 +437,50 @@ window.onload = Task.async(function* () {
|
||||
},
|
||||
};
|
||||
|
||||
case "testMoreProp":
|
||||
return {
|
||||
"type": "object",
|
||||
"class": "Object",
|
||||
"actor": "server1.conn0.obj342",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 4,
|
||||
"preview": {
|
||||
"kind": "Object",
|
||||
"ownProperties": {
|
||||
"a": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": {
|
||||
"type": "undefined"
|
||||
}
|
||||
},
|
||||
"b": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": 1
|
||||
},
|
||||
"more": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": 2
|
||||
},
|
||||
"d": {
|
||||
"configurable": true,
|
||||
"enumerable": true,
|
||||
"writable": true,
|
||||
"value": 3
|
||||
}
|
||||
},
|
||||
"ownPropertiesLength": 4,
|
||||
"safeGetterValues": {}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -30,6 +30,9 @@ window.onload = Task.async(function* () {
|
||||
|
||||
// Test that properties are rendered as expected by PropRep
|
||||
yield testNested();
|
||||
|
||||
// Test that 'more' property doesn't clobber the caption.
|
||||
yield testMoreProp();
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
@ -183,7 +186,37 @@ window.onload = Task.async(function* () {
|
||||
|
||||
testRepRenderModes(modeTests, "testNestedObject", componentUnderTest, stub);
|
||||
}
|
||||
});
|
||||
|
||||
function testMoreProp() {
|
||||
const stub = {
|
||||
a: undefined,
|
||||
b: 1,
|
||||
'more': 2,
|
||||
d: 3
|
||||
};
|
||||
const defaultOutput = `Object { b: 1, more: 2, d: 3, 1 more… }`;
|
||||
|
||||
const modeTests = [
|
||||
{
|
||||
mode: undefined,
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "tiny",
|
||||
expectedOutput: `Object`,
|
||||
},
|
||||
{
|
||||
mode: "short",
|
||||
expectedOutput: defaultOutput,
|
||||
},
|
||||
{
|
||||
mode: "long",
|
||||
expectedOutput: defaultOutput,
|
||||
}
|
||||
];
|
||||
|
||||
testRepRenderModes(modeTests, "testMoreProp", componentUnderTest, stub);
|
||||
}});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -366,11 +366,11 @@ checkbox:-moz-focusring {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.devtools-button:hover:empty::before,
|
||||
.devtools-button:hover:empty:not(:disabled):before,
|
||||
.devtools-button.checked:empty::before,
|
||||
.devtools-button[checked]:empty::before,
|
||||
.devtools-button[open]:empty::before,
|
||||
.devtools-toolbarbutton:not([label]):hover > image,
|
||||
.devtools-toolbarbutton:not([label]):not([disabled=true]):hover > image,
|
||||
.devtools-toolbarbutton:not([label])[checked=true] > image,
|
||||
.devtools-toolbarbutton:not([label])[open=true] > image {
|
||||
opacity: 1;
|
||||
|
@ -1186,10 +1186,10 @@ JSTerm.prototype = {
|
||||
this._autocompletePopupNavigated = true;
|
||||
}
|
||||
} else {
|
||||
this.hud.outputWrapper.scrollTop =
|
||||
this.hud.outputScroller.scrollTop =
|
||||
Math.max(0,
|
||||
this.hud.outputWrapper.scrollTop -
|
||||
this.hud.outputWrapper.clientHeight
|
||||
this.hud.outputScroller.scrollTop -
|
||||
this.hud.outputScroller.clientHeight
|
||||
);
|
||||
}
|
||||
event.preventDefault();
|
||||
@ -1202,10 +1202,10 @@ JSTerm.prototype = {
|
||||
this._autocompletePopupNavigated = true;
|
||||
}
|
||||
} else {
|
||||
this.hud.outputWrapper.scrollTop =
|
||||
Math.min(this.hud.outputWrapper.scrollHeight,
|
||||
this.hud.outputWrapper.scrollTop +
|
||||
this.hud.outputWrapper.clientHeight
|
||||
this.hud.outputScroller.scrollTop =
|
||||
Math.min(this.hud.outputScroller.scrollHeight,
|
||||
this.hud.outputScroller.scrollTop +
|
||||
this.hud.outputScroller.clientHeight
|
||||
);
|
||||
}
|
||||
event.preventDefault();
|
||||
@ -1216,7 +1216,7 @@ JSTerm.prototype = {
|
||||
this.autocompletePopup.selectedIndex = 0;
|
||||
event.preventDefault();
|
||||
} else if (inputValue.length <= 0) {
|
||||
this.hud.outputWrapper.scrollTop = 0;
|
||||
this.hud.outputScroller.scrollTop = 0;
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
@ -1227,8 +1227,8 @@ JSTerm.prototype = {
|
||||
this.autocompletePopup.itemCount - 1;
|
||||
event.preventDefault();
|
||||
} else if (inputValue.length <= 0) {
|
||||
this.hud.outputWrapper.scrollTop =
|
||||
this.hud.outputWrapper.scrollHeight;
|
||||
this.hud.outputScroller.scrollTop =
|
||||
this.hud.outputScroller.scrollHeight;
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
|
@ -31,7 +31,7 @@ const ConsoleOutput = createClass({
|
||||
|
||||
componentDidMount() {
|
||||
scrollToBottom(this.outputNode);
|
||||
this.props.serviceContainer.attachRefToHud("outputWrapper", this.outputNode);
|
||||
this.props.serviceContainer.attachRefToHud("outputScroller", this.outputNode);
|
||||
},
|
||||
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
|
@ -19,6 +19,5 @@ const NewConsoleOutputWrapper = BrowserLoader({
|
||||
window}).require("./new-console-output-wrapper");
|
||||
|
||||
this.NewConsoleOutput = function (parentNode, jsterm, toolbox, owner, serviceContainer) {
|
||||
console.log("Creating NewConsoleOutput", parentNode, NewConsoleOutputWrapper);
|
||||
return new NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, serviceContainer);
|
||||
};
|
||||
|
@ -20,33 +20,33 @@ add_task(function* () {
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
info("Web Console opened");
|
||||
|
||||
const outputWrapper = hud.ui.outputWrapper;
|
||||
const outputScroller = hud.ui.outputScroller;
|
||||
|
||||
yield waitFor(() => findMessages(hud, "").length == 100);
|
||||
|
||||
let currentPosition = outputWrapper.scrollTop;
|
||||
let currentPosition = outputScroller.scrollTop;
|
||||
const bottom = currentPosition;
|
||||
|
||||
EventUtils.sendMouseEvent({type: "click"}, hud.jsterm.inputNode);
|
||||
|
||||
// Page up.
|
||||
EventUtils.synthesizeKey("VK_PAGE_UP", {});
|
||||
isnot(outputWrapper.scrollTop, currentPosition,
|
||||
isnot(outputScroller.scrollTop, currentPosition,
|
||||
"scroll position changed after page up");
|
||||
|
||||
// Page down.
|
||||
currentPosition = outputWrapper.scrollTop;
|
||||
currentPosition = outputScroller.scrollTop;
|
||||
EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
|
||||
ok(outputWrapper.scrollTop > currentPosition,
|
||||
ok(outputScroller.scrollTop > currentPosition,
|
||||
"scroll position now at bottom");
|
||||
|
||||
// Home
|
||||
EventUtils.synthesizeKey("VK_HOME", {});
|
||||
is(outputWrapper.scrollTop, 0, "scroll position now at top");
|
||||
is(outputScroller.scrollTop, 0, "scroll position now at top");
|
||||
|
||||
// End
|
||||
EventUtils.synthesizeKey("VK_END", {});
|
||||
let scrollTop = outputWrapper.scrollTop;
|
||||
let scrollTop = outputScroller.scrollTop;
|
||||
ok(scrollTop > 0 && Math.abs(scrollTop - bottom) <= 5,
|
||||
"scroll position now at bottom");
|
||||
|
||||
@ -66,6 +66,6 @@ add_task(function* () {
|
||||
info("try ctrl-f to focus filter");
|
||||
synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
|
||||
ok(!hud.jsterm.inputNode.getAttribute("focused"), "jsterm input is not focused");
|
||||
is(hud.ui.filterBox, outputWrapper.ownerDocument.activeElement,
|
||||
is(hud.ui.filterBox, outputScroller.ownerDocument.activeElement,
|
||||
"filter input is focused");
|
||||
});
|
||||
|
@ -542,37 +542,19 @@ WebConsoleFrame.prototype = {
|
||||
&& !this.owner.target.chrome
|
||||
&& Services.prefs.getBoolPref(PREF_NEW_FRONTEND_ENABLED);
|
||||
|
||||
this._initDefaultFilterPrefs();
|
||||
this.outputNode = this.document.getElementById("output-container");
|
||||
this.outputWrapper = this.document.getElementById("output-wrapper");
|
||||
this.completeNode = this.document.querySelector(".jsterm-complete-node");
|
||||
this.inputNode = this.document.querySelector(".jsterm-input-node");
|
||||
|
||||
// Register the controller to handle "select all" properly.
|
||||
this._commandController = new CommandController(this);
|
||||
this.window.controllers.insertControllerAt(0, this._commandController);
|
||||
|
||||
this._contextMenuHandler = new ConsoleContextMenu(this);
|
||||
|
||||
let doc = this.document;
|
||||
|
||||
this.filterBox = doc.querySelector(".hud-filter-box");
|
||||
this.outputNode = doc.getElementById("output-container");
|
||||
this.outputWrapper = doc.getElementById("output-wrapper");
|
||||
|
||||
this.completeNode = doc.querySelector(".jsterm-complete-node");
|
||||
this.inputNode = doc.querySelector(".jsterm-input-node");
|
||||
|
||||
this._setFilterTextBoxEvents();
|
||||
this._initFilterButtons();
|
||||
// In the old frontend, the area that scrolls is outputWrapper, but in the new
|
||||
// frontend this will be reassigned.
|
||||
this.outputScroller = this.outputWrapper;
|
||||
|
||||
// Update the character width and height needed for the popup offset
|
||||
// calculations.
|
||||
this._updateCharSize();
|
||||
|
||||
let clearButton =
|
||||
doc.getElementsByClassName("webconsole-clear-console-button")[0];
|
||||
clearButton.addEventListener("command", () => {
|
||||
this.owner._onClearButton();
|
||||
this.jsterm.clearOutput(true);
|
||||
});
|
||||
|
||||
this.jsterm = new JSTerm(this);
|
||||
this.jsterm.init();
|
||||
|
||||
@ -596,10 +578,27 @@ WebConsoleFrame.prototype = {
|
||||
|
||||
this.newConsoleOutput = new this.window.NewConsoleOutput(
|
||||
this.experimentalOutputNode, this.jsterm, toolbox, this.owner);
|
||||
console.log("Created newConsoleOutput", this.newConsoleOutput);
|
||||
|
||||
let filterToolbar = doc.querySelector(".hud-console-filter-toolbar");
|
||||
let filterToolbar = this.document.querySelector(".hud-console-filter-toolbar");
|
||||
filterToolbar.hidden = true;
|
||||
} else {
|
||||
// Register the controller to handle "select all" properly.
|
||||
this._commandController = new CommandController(this);
|
||||
this.window.controllers.insertControllerAt(0, this._commandController);
|
||||
|
||||
this._contextMenuHandler = new ConsoleContextMenu(this);
|
||||
|
||||
this._initDefaultFilterPrefs();
|
||||
this.filterBox = this.document.querySelector(".hud-filter-box");
|
||||
this._setFilterTextBoxEvents();
|
||||
this._initFilterButtons();
|
||||
let clearButton =
|
||||
this.document.getElementsByClassName("webconsole-clear-console-button")[0];
|
||||
clearButton.addEventListener("command", () => {
|
||||
this.owner._onClearButton();
|
||||
this.jsterm.clearOutput(true);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.resize();
|
||||
|
@ -272,6 +272,7 @@ GK_ATOM(dataType, "data-type")
|
||||
GK_ATOM(dateTime, "date-time")
|
||||
GK_ATOM(datasources, "datasources")
|
||||
GK_ATOM(datetime, "datetime")
|
||||
GK_ATOM(datetimebox, "datetimebox")
|
||||
GK_ATOM(dblclick, "dblclick")
|
||||
GK_ATOM(dd, "dd")
|
||||
GK_ATOM(debug, "debug")
|
||||
@ -1985,6 +1986,7 @@ GK_ATOM(colorControlFrame, "colorControlFrame")
|
||||
GK_ATOM(columnSetFrame, "ColumnSetFrame")
|
||||
GK_ATOM(comboboxControlFrame, "ComboboxControlFrame")
|
||||
GK_ATOM(comboboxDisplayFrame, "ComboboxDisplayFrame")
|
||||
GK_ATOM(dateTimeControlFrame, "DateTimeControlFrame")
|
||||
GK_ATOM(deckFrame, "DeckFrame")
|
||||
GK_ATOM(detailsFrame, "DetailsFrame")
|
||||
GK_ATOM(fieldSetFrame, "FieldSetFrame")
|
||||
|
15
dom/canvas/test/reftest/mozCurrentTransform-ref.html
Normal file
15
dom/canvas/test/reftest/mozCurrentTransform-ref.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<canvas id="canvas" width="150" height="150"></canvas>
|
||||
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.transform(1,0.5,-0.5,1,30,10);
|
||||
ctx.fillStyle = '#f00';
|
||||
ctx.fillRect(0, 0, 100, 100);
|
||||
|
||||
</script>
|
||||
</body></html>
|
22
dom/canvas/test/reftest/mozCurrentTransform.html
Normal file
22
dom/canvas/test/reftest/mozCurrentTransform.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<head>
|
||||
<canvas id="canvas" width="150" height="150"></canvas>
|
||||
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.transform(1,0.5,-0.5,1,30,10);
|
||||
setTimeout(function() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var transform = ctx.mozCurrentTransform;
|
||||
ctx.mozCurrentTransform = transform;
|
||||
ctx.fillStyle = '#f00';
|
||||
ctx.fillRect(0, 0, 100, 100);
|
||||
document.documentElement.removeAttribute("class");
|
||||
}, 10)
|
||||
|
||||
</script>
|
||||
</body></html>
|
22
dom/canvas/test/reftest/mozCurrentTransformInverse.html
Normal file
22
dom/canvas/test/reftest/mozCurrentTransformInverse.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<head>
|
||||
<canvas id="canvas" width="150" height="150"></canvas>
|
||||
|
||||
<script>
|
||||
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.transform(1,0.5,-0.5,1,30,10);
|
||||
setTimeout(function() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var transform = ctx.mozCurrentTransformInverse;
|
||||
ctx.mozCurrentTransformInverse = transform;
|
||||
ctx.fillStyle = '#f00';
|
||||
ctx.fillRect(0, 0, 100, 100);
|
||||
document.documentElement.removeAttribute("class");
|
||||
}, 10)
|
||||
|
||||
</script>
|
||||
</body></html>
|
@ -166,3 +166,7 @@ fuzzy-if(azureSkia,16,2) fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.
|
||||
|
||||
# Canvas Filter Reftests
|
||||
include filters/reftest.list
|
||||
|
||||
# Bug 1305963
|
||||
== mozCurrentTransform.html mozCurrentTransform-ref.html
|
||||
== mozCurrentTransformInverse.html mozCurrentTransform-ref.html
|
||||
|
@ -649,6 +649,8 @@ HTMLImageElement::UnbindFromTree(bool aDeep, bool aNullParent)
|
||||
}
|
||||
}
|
||||
|
||||
mLastSelectedSource = nullptr;
|
||||
|
||||
nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
|
||||
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
|
||||
}
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include "nsIIOService.h"
|
||||
#include "nsDocument.h"
|
||||
#include "nsAttrValueOrString.h"
|
||||
#include "nsDateTimeControlFrame.h"
|
||||
|
||||
#include "nsPresState.h"
|
||||
#include "nsIDOMEvent.h"
|
||||
@ -2707,6 +2708,82 @@ HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
|
||||
SetFilesOrDirectories(array, true);
|
||||
}
|
||||
|
||||
void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue)
|
||||
{
|
||||
if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
aValue = *mDateTimeInputBoxValue;
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::UpdateDateTimeInputBox(const DateTimeValue& aValue)
|
||||
{
|
||||
if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (frame) {
|
||||
frame->SetValueFromPicker(aValue);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::SetDateTimePickerState(bool aOpen)
|
||||
{
|
||||
if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (frame) {
|
||||
frame->SetPickerState(aOpen);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue)
|
||||
{
|
||||
if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDateTimeInputBoxValue = new DateTimeValue(aInitialValue);
|
||||
nsContentUtils::DispatchChromeEvent(OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLInputElement*>(this),
|
||||
NS_LITERAL_STRING("MozOpenDateTimePicker"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue)
|
||||
{
|
||||
if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDateTimeInputBoxValue = new DateTimeValue(aValue);
|
||||
nsContentUtils::DispatchChromeEvent(OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLInputElement*>(this),
|
||||
NS_LITERAL_STRING("MozUpdateDateTimePicker"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::CloseDateTimePicker()
|
||||
{
|
||||
if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsContentUtils::DispatchChromeEvent(OwnerDoc(),
|
||||
static_cast<nsIDOMHTMLInputElement*>(this),
|
||||
NS_LITERAL_STRING("MozCloseDateTimePicker"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLInputElement::MozIsTextField(bool aExcludePassword)
|
||||
{
|
||||
@ -2733,6 +2810,28 @@ HTMLInputElement::GetOwnerNumberControl()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HTMLInputElement*
|
||||
HTMLInputElement::GetOwnerDateTimeControl()
|
||||
{
|
||||
if (IsInNativeAnonymousSubtree() &&
|
||||
mType == NS_FORM_INPUT_TEXT &&
|
||||
GetParent() &&
|
||||
GetParent()->GetParent() &&
|
||||
GetParent()->GetParent()->GetParent() &&
|
||||
GetParent()->GetParent()->GetParent()->GetParent()) {
|
||||
// Yes, this is very very deep.
|
||||
HTMLInputElement* ownerDateTimeControl =
|
||||
HTMLInputElement::FromContentOrNull(
|
||||
GetParent()->GetParent()->GetParent()->GetParent());
|
||||
if (ownerDateTimeControl &&
|
||||
ownerDateTimeControl->mType == NS_FORM_INPUT_TIME) {
|
||||
return ownerDateTimeControl;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
|
||||
{
|
||||
@ -2740,6 +2839,19 @@ HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::SetUserInput(const nsAString& aInput,
|
||||
const mozilla::Maybe<nsIPrincipal*>& aPrincipal) {
|
||||
MOZ_ASSERT(aPrincipal.isSome());
|
||||
|
||||
if (mType == NS_FORM_INPUT_FILE &&
|
||||
!nsContentUtils::IsSystemPrincipal(aPrincipal.value())) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetUserInput(aInput);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLInputElement::SetUserInput(const nsAString& aValue)
|
||||
{
|
||||
@ -3164,6 +3276,12 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
|
||||
if (frame) {
|
||||
frame->UpdateForValueChange();
|
||||
}
|
||||
} else if (mType == NS_FORM_INPUT_TIME &&
|
||||
!IsExperimentalMobileType(mType)) {
|
||||
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (frame) {
|
||||
frame->UpdateInputBoxValue();
|
||||
}
|
||||
}
|
||||
if (!mParserCreating) {
|
||||
OnValueChanged(/* aNotify = */ true,
|
||||
@ -3466,6 +3584,15 @@ HTMLInputElement::Blur(ErrorResult& aError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
|
||||
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (frame) {
|
||||
frame->HandleBlurEvent();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsGenericHTMLElement::Blur(aError);
|
||||
}
|
||||
|
||||
@ -3485,6 +3612,14 @@ HTMLInputElement::Focus(ErrorResult& aError)
|
||||
}
|
||||
}
|
||||
|
||||
if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
|
||||
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (frame) {
|
||||
frame->HandleFocusEvent();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mType != NS_FORM_INPUT_FILE) {
|
||||
nsGenericHTMLElement::Focus(aError);
|
||||
return;
|
||||
@ -3788,7 +3923,7 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
|
||||
// Experimental mobile types rely on the system UI to prevent users to not
|
||||
// set invalid values but we have to be extra-careful. Especially if the
|
||||
// option has been enabled on desktop.
|
||||
if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
|
||||
if (IsExperimentalMobileType(mType)) {
|
||||
nsAutoString aValue;
|
||||
GetValueInternal(aValue);
|
||||
nsresult rv =
|
||||
@ -3809,6 +3944,18 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
if (mType == NS_FORM_INPUT_TIME &&
|
||||
!IsExperimentalMobileType(mType) &&
|
||||
aVisitor.mEvent->mMessage == eFocus &&
|
||||
aVisitor.mEvent->mOriginalTarget == this) {
|
||||
// If original target is this and not the anonymous text control, we should
|
||||
// pass the focus to the anonymous text control.
|
||||
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (frame) {
|
||||
frame->HandleFocusEvent();
|
||||
}
|
||||
}
|
||||
|
||||
if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
|
||||
if (mNumberControlSpinnerIsSpinning) {
|
||||
// If the timer is running the user has depressed the mouse on one of the
|
||||
@ -6682,6 +6829,11 @@ HTMLInputElement::AddStates(EventStates aStates)
|
||||
HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
|
||||
if (ownerNumberControl) {
|
||||
ownerNumberControl->AddStates(focusStates);
|
||||
} else {
|
||||
HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
|
||||
if (ownerDateTimeControl) {
|
||||
ownerDateTimeControl->AddStates(focusStates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6698,6 +6850,11 @@ HTMLInputElement::RemoveStates(EventStates aStates)
|
||||
HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
|
||||
if (ownerNumberControl) {
|
||||
ownerNumberControl->RemoveStates(focusStates);
|
||||
} else {
|
||||
HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
|
||||
if (ownerDateTimeControl) {
|
||||
ownerDateTimeControl->RemoveStates(focusStates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6875,12 +7032,14 @@ HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t*
|
||||
#endif
|
||||
|
||||
if (mType == NS_FORM_INPUT_FILE ||
|
||||
mType == NS_FORM_INPUT_NUMBER) {
|
||||
mType == NS_FORM_INPUT_NUMBER ||
|
||||
mType == NS_FORM_INPUT_TIME) {
|
||||
if (aTabIndex) {
|
||||
// We only want our native anonymous child to be tabable to, not ourself.
|
||||
*aTabIndex = -1;
|
||||
}
|
||||
if (mType == NS_FORM_INPUT_NUMBER) {
|
||||
if (mType == NS_FORM_INPUT_NUMBER ||
|
||||
mType == NS_FORM_INPUT_TIME) {
|
||||
*aIsFocusable = true;
|
||||
} else {
|
||||
*aIsFocusable = defaultFocusable;
|
||||
|
@ -772,7 +772,24 @@ public:
|
||||
void MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles);
|
||||
void MozSetDirectory(const nsAString& aDirectoryPath, ErrorResult& aRv);
|
||||
|
||||
/*
|
||||
* The following functions are called from datetime picker to let input box
|
||||
* know the current state of the picker or to update the input box on changes.
|
||||
*/
|
||||
void GetDateTimeInputBoxValue(DateTimeValue& aValue);
|
||||
void UpdateDateTimeInputBox(const DateTimeValue& aValue);
|
||||
void SetDateTimePickerState(bool aOpen);
|
||||
|
||||
/*
|
||||
* The following functions are called from datetime input box XBL to control
|
||||
* and update the picker.
|
||||
*/
|
||||
void OpenDateTimePicker(const DateTimeValue& aInitialValue);
|
||||
void UpdateDateTimePicker(const DateTimeValue& aValue);
|
||||
void CloseDateTimePicker();
|
||||
|
||||
HTMLInputElement* GetOwnerNumberControl();
|
||||
HTMLInputElement* GetOwnerDateTimeControl();
|
||||
|
||||
void StartNumberControlSpinnerSpin();
|
||||
enum SpinnerStopState {
|
||||
@ -803,7 +820,8 @@ public:
|
||||
|
||||
nsIEditor* GetEditor();
|
||||
|
||||
// XPCOM SetUserInput() is OK
|
||||
void SetUserInput(const nsAString& aInput,
|
||||
const mozilla::Maybe<nsIPrincipal*>& aPrincipal);
|
||||
|
||||
// XPCOM GetPhonetic() is OK
|
||||
|
||||
@ -1471,6 +1489,12 @@ protected:
|
||||
*/
|
||||
Decimal mRangeThumbDragStartValue;
|
||||
|
||||
/**
|
||||
* Current value in the input box, in DateTimeValue dictionary format, see
|
||||
* HTMLInputElement.webidl for details.
|
||||
*/
|
||||
nsAutoPtr<DateTimeValue> mDateTimeInputBoxValue;
|
||||
|
||||
/**
|
||||
* The selection properties cache for number controls. This is needed because
|
||||
* the number controls don't recycle their text field, so the normal cache in
|
||||
@ -1561,7 +1585,8 @@ private:
|
||||
static bool MayFireChangeOnBlur(uint8_t aType) {
|
||||
return IsSingleLineTextControl(false, aType) ||
|
||||
aType == NS_FORM_INPUT_RANGE ||
|
||||
aType == NS_FORM_INPUT_NUMBER;
|
||||
aType == NS_FORM_INPUT_NUMBER ||
|
||||
aType == NS_FORM_INPUT_TIME;
|
||||
}
|
||||
|
||||
struct nsFilePickerFilter {
|
||||
|
@ -18,6 +18,7 @@ MOCHITEST_CHROME_MANIFESTS += [
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIDateTimeInputArea.idl',
|
||||
'nsIFormSubmitObserver.idl',
|
||||
'nsIHTMLMenu.idl',
|
||||
'nsIImageDocument.idl',
|
||||
|
36
dom/html/nsIDateTimeInputArea.idl
Normal file
36
dom/html/nsIDateTimeInputArea.idl
Normal file
@ -0,0 +1,36 @@
|
||||
/* -*- 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"
|
||||
|
||||
[scriptable, uuid(465c0cc3-24cb-48ce-af1a-b18402326b05)]
|
||||
interface nsIDateTimeInputArea : nsISupports
|
||||
{
|
||||
/**
|
||||
* Called from DOM/Layout when input element value has changed.
|
||||
*/
|
||||
void notifyInputElementValueChanged();
|
||||
|
||||
/**
|
||||
* Called when date/time picker value has changed.
|
||||
*/
|
||||
void setValueFromPicker(in jsval value);
|
||||
|
||||
/**
|
||||
* Called from DOM/Layout to set focus on inner text box.
|
||||
*/
|
||||
void focusInnerTextBox();
|
||||
|
||||
/**
|
||||
* Called from DOM/Layout to blur inner text box.
|
||||
*/
|
||||
void blurInnerTextBox();
|
||||
|
||||
/**
|
||||
* Set the current state of the picker, true if it's opened, false otherwise.
|
||||
*/
|
||||
void setPickerState(in boolean isOpen);
|
||||
};
|
@ -266,8 +266,11 @@ nsIFormControl::IsSingleLineTextControl(bool aExcludePassword, uint32_t aType)
|
||||
aType == NS_FORM_INPUT_TEL ||
|
||||
aType == NS_FORM_INPUT_URL ||
|
||||
// TODO: those are temporary until bug 773205 is fixed.
|
||||
aType == NS_FORM_INPUT_DATE ||
|
||||
#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 ||
|
||||
aType == NS_FORM_INPUT_MONTH ||
|
||||
aType == NS_FORM_INPUT_WEEK ||
|
||||
(!aExcludePassword && aType == NS_FORM_INPUT_PASSWORD);
|
||||
|
22
dom/html/reftests/autofocus/input-time-ref.html
Normal file
22
dom/html/reftests/autofocus/input-time-ref.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<!-- In this case we're using reftest-wait to make sure the test doesn't
|
||||
get snapshotted before it's been focused. We're not testing
|
||||
invalidation so we don't need to listen for MozReftestInvalidate.
|
||||
-->
|
||||
<head>
|
||||
<script>
|
||||
function focusHandler() {
|
||||
setTimeout(function() {
|
||||
document.documentElement.removeAttribute("class");
|
||||
}, 0);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="document.getElementById('t').focus();">
|
||||
<input type="time" id="t" onfocus="focusHandler();"
|
||||
style="-moz-appearance: none;">
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
22
dom/html/reftests/autofocus/input-time.html
Normal file
22
dom/html/reftests/autofocus/input-time.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<!-- In this case we're using reftest-wait to make sure the test doesn't
|
||||
get snapshotted before it's been focused. We're not testing
|
||||
invalidation so we don't need to listen for MozReftestInvalidate.
|
||||
-->
|
||||
<head>
|
||||
<script>
|
||||
function focusHandler() {
|
||||
setTimeout(function() {
|
||||
document.documentElement.removeAttribute("class");
|
||||
}, 0);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="time" autofocus onfocus="focusHandler();"
|
||||
style="-moz-appearance: none;">
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
default-preferences pref(dom.forms.number,true)
|
||||
default-preferences pref(dom.forms.number,true) pref(dom.forms.datetime,true)
|
||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-load.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-create.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-number.html input-number-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-time.html input-time-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == button-load.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == button-create.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == textarea-load.html textarea-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
|
1
dom/html/reftests/image-load-shortcircuit-ref.html
Normal file
1
dom/html/reftests/image-load-shortcircuit-ref.html
Normal file
@ -0,0 +1 @@
|
||||
<div><img src=pass.png></div>
|
8
dom/html/reftests/image-load-shortcircuit.html
Normal file
8
dom/html/reftests/image-load-shortcircuit.html
Normal file
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<div></div>
|
||||
<script>
|
||||
var d = (new DOMParser()).parseFromString("<img src=pass.png>", "text/html");
|
||||
var n = d.adoptNode(d.querySelector('img'));
|
||||
document.querySelector('div').appendChild(n);
|
||||
</script>
|
||||
</html>
|
@ -35,6 +35,7 @@ skip-if(Android||B2G) == 649134-2.html 649134-2-ref.html
|
||||
== bug502168-1_malformed.html bug502168-1_well-formed.html
|
||||
|
||||
== responsive-image-load-shortcircuit.html responsive-image-load-shortcircuit-ref.html
|
||||
== image-load-shortcircuit.html image-load-shortcircuit-ref.html
|
||||
|
||||
# Test that image documents taken into account CSS properties like
|
||||
# image-orientation when determining the size of the image.
|
||||
|
@ -35,6 +35,10 @@ skip-if = buildapp == 'mulet'
|
||||
skip-if = android_version == '18' # Android, bug 1147974
|
||||
[test_input_color_picker_update.html]
|
||||
skip-if = android_version == '18' # Android, bug 1147974
|
||||
[test_input_datetime_focus_blur.html]
|
||||
skip-if = os == "android" || appname == "b2g"
|
||||
[test_input_datetime_tabindex.html]
|
||||
skip-if = os == "android" || appname == "b2g"
|
||||
[test_input_defaultValue.html]
|
||||
[test_input_email.html]
|
||||
[test_input_event.html]
|
||||
@ -64,6 +68,8 @@ skip-if = (toolkit == 'gonk' && debug) #debug-only failure; bug 926546
|
||||
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
|
||||
[test_input_sanitization.html]
|
||||
[test_input_textarea_set_value_no_scroll.html]
|
||||
[test_input_time_key_events.html]
|
||||
skip-if = os == "android" || appname == "b2g"
|
||||
[test_input_types_pref.html]
|
||||
[test_input_typing_sanitization.html]
|
||||
skip-if = buildapp == 'mulet'
|
||||
|
58
dom/html/test/forms/test_input_datetime_focus_blur.html
Normal file
58
dom/html/test/forms/test_input_datetime_focus_blur.html
Normal file
@ -0,0 +1,58 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
|
||||
-->
|
||||
<head>
|
||||
<title>Test focus/blur behaviour for <input type='time'></title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=1288591">Mozilla Bug 1288591</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<input id="input" type="time">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Test for Bug 1288591.
|
||||
* This test checks whether date/time input types' .focus()/.blur() works
|
||||
* correctly. This test also checks when focusing on an date/time input element,
|
||||
* the focus is redirected to the anonymous text control, but the
|
||||
* document.activeElement still returns date/time input element.
|
||||
**/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
test();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
function test() {
|
||||
let time = document.getElementById("input");
|
||||
time.focus();
|
||||
|
||||
// The active element returns the input type=time.
|
||||
let activeElement = document.activeElement;
|
||||
is(activeElement, time, "activeElement should be the time element");
|
||||
is(activeElement.localName, "input", "activeElement should be an input element");
|
||||
is(activeElement.type, "time", "activeElement should be of type time");
|
||||
|
||||
// Use FocusManager to check that the actual focus is on the anonymous
|
||||
// text control.
|
||||
let fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"]
|
||||
.getService(SpecialPowers.Ci.nsIFocusManager);
|
||||
let focusedElement = fm.focusedElement;
|
||||
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");
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
72
dom/html/test/forms/test_input_datetime_tabindex.html
Normal file
72
dom/html/test/forms/test_input_datetime_tabindex.html
Normal file
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
|
||||
-->
|
||||
<head>
|
||||
<title>Test tabindex attribute for <input type='time'></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"/>
|
||||
</head>
|
||||
<body>
|
||||
<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="time1" type="time" tabindex="0">
|
||||
<input id="time2" type="time" tabindex="-1">
|
||||
<input id="time3" type="time" 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
|
||||
* correctly.
|
||||
**/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
test();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
function test() {
|
||||
let time1 = document.getElementById("time1");
|
||||
let time2 = document.getElementById("time2");
|
||||
let time3 = document.getElementById("time3");
|
||||
|
||||
time1.focus();
|
||||
is(document.activeElement, time1,
|
||||
"input element with tabindex=0 is focusable");
|
||||
|
||||
// Advance to time1 minute field
|
||||
synthesizeKey("VK_TAB", {});
|
||||
is(document.activeElement, time1,
|
||||
"input element with tabindex=0 is tabbable");
|
||||
|
||||
// Advance to time1 AM/PM field
|
||||
synthesizeKey("VK_TAB", {});
|
||||
is(document.activeElement, time1,
|
||||
"input element with tabindex=0 is tabbable");
|
||||
|
||||
// Advance to next element
|
||||
synthesizeKey("VK_TAB", {});
|
||||
is(document.activeElement, time3,
|
||||
"input element with tabindex=-1 is not tabbable");
|
||||
|
||||
time2.focus();
|
||||
is(document.activeElement, time2,
|
||||
"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,
|
||||
"element with tabindex changed to -1 should not be tabbable");
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
197
dom/html/test/forms/test_input_time_key_events.html
Normal file
197
dom/html/test/forms/test_input_time_key_events.html
Normal file
@ -0,0 +1,197 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
|
||||
-->
|
||||
<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=1288591">Mozilla Bug 1288591</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<input id="input" type="time">
|
||||
</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 1030 and select AM.
|
||||
keys: ["1030", "VK_DOWN"],
|
||||
initialVal: "",
|
||||
expectedVal: "10:30"
|
||||
},
|
||||
{
|
||||
// Type 3 in the hour field will automatically advance to the minute field.
|
||||
keys: ["330", "VK_DOWN"],
|
||||
initialVal: "",
|
||||
expectedVal: "03:30"
|
||||
},
|
||||
{
|
||||
// Type 5 in the hour field will automatically advance to the minute field.
|
||||
// Type 7 in the minute field will automatically advance to the AM/PM field.
|
||||
keys: ["57", "VK_DOWN"],
|
||||
initialVal: "",
|
||||
expectedVal: "05:07"
|
||||
},
|
||||
{
|
||||
// Advance to AM/PM field and change to PM.
|
||||
keys: ["VK_TAB", "VK_TAB", "VK_DOWN"],
|
||||
initialVal: "10:30",
|
||||
expectedVal: "22:30"
|
||||
},
|
||||
{
|
||||
// Right key should do the same thing as TAB key.
|
||||
keys: ["VK_RIGHT", "VK_RIGHT", "VK_DOWN"],
|
||||
initialVal: "10:30",
|
||||
expectedVal: "22:30"
|
||||
},
|
||||
{
|
||||
// Advance to minute field then back to hour field and decrement.
|
||||
keys: ["VK_RIGHT", "VK_LEFT", "VK_DOWN"],
|
||||
initialVal: "10:30",
|
||||
expectedVal: "09:30"
|
||||
},
|
||||
{
|
||||
// Focus starts on the first field, hour in this case, and increment.
|
||||
keys: ["VK_UP"],
|
||||
initialVal: "16:00",
|
||||
expectedVal: "17:00"
|
||||
},
|
||||
{
|
||||
// Advance to minute field and decrement.
|
||||
keys: ["VK_TAB", "VK_DOWN"],
|
||||
initialVal: "16:00",
|
||||
expectedVal: "16:59"
|
||||
},
|
||||
{
|
||||
// Advance to minute field and increment.
|
||||
keys: ["VK_TAB", "VK_UP"],
|
||||
initialVal: "16:59",
|
||||
expectedVal: "16:00"
|
||||
},
|
||||
{
|
||||
// PageUp on hour field increments hour by 3.
|
||||
keys: ["VK_PAGE_UP"],
|
||||
initialVal: "05:00",
|
||||
expectedVal: "08:00"
|
||||
},
|
||||
{
|
||||
// PageDown on hour field decrements hour by 3.
|
||||
keys: ["VK_PAGE_DOWN"],
|
||||
initialVal: "05:00",
|
||||
expectedVal: "02:00"
|
||||
},
|
||||
{
|
||||
// PageUp on minute field increments minute by 10.
|
||||
keys: ["VK_TAB", "VK_PAGE_UP"],
|
||||
initialVal: "14:00",
|
||||
expectedVal: "14:10"
|
||||
},
|
||||
{
|
||||
// PageDown on minute field decrements minute by 10.
|
||||
keys: ["VK_TAB", "VK_PAGE_DOWN"],
|
||||
initialVal: "14:00",
|
||||
expectedVal: "14:50"
|
||||
},
|
||||
{
|
||||
// Home key on hour field sets it to the minimum hour, which is 1 in 12-hour
|
||||
// clock.
|
||||
keys: ["VK_HOME"],
|
||||
initialVal: "03:10",
|
||||
expectedVal: "01:10"
|
||||
},
|
||||
{
|
||||
// End key on hour field sets it to the maximum hour, which is 12 in 12-hour
|
||||
// clock.
|
||||
keys: ["VK_END"],
|
||||
initialVal: "03:10",
|
||||
expectedVal: "00:10"
|
||||
},
|
||||
{
|
||||
// Home key on minute field sets it to the minimum minute, which is 0.
|
||||
keys: ["VK_TAB", "VK_HOME"],
|
||||
initialVal: "19:30",
|
||||
expectedVal: "19:00"
|
||||
},
|
||||
{
|
||||
// End key on minute field sets it to the minimum minute, which is 59.
|
||||
keys: ["VK_TAB", "VK_END"],
|
||||
initialVal: "19:30",
|
||||
expectedVal: "19:59"
|
||||
},
|
||||
// Second field will show up when needed.
|
||||
{
|
||||
// PageUp on second field increments second by 10.
|
||||
keys: ["VK_TAB", "VK_TAB", "VK_PAGE_UP"],
|
||||
initialVal: "08:10:10",
|
||||
expectedVal: "08:10:20"
|
||||
},
|
||||
{
|
||||
// PageDown on second field increments second by 10.
|
||||
keys: ["VK_TAB", "VK_TAB", "VK_PAGE_DOWN"],
|
||||
initialVal: "08:10:10",
|
||||
expectedVal: "08:10:00"
|
||||
},
|
||||
{
|
||||
// Home key on second field sets it to the minimum second, which is 0.
|
||||
keys: ["VK_TAB", "VK_TAB", "VK_HOME"],
|
||||
initialVal: "16:00:30",
|
||||
expectedVal: "16:00:00"
|
||||
},
|
||||
{
|
||||
// End key on second field sets it to the minimum second, which is 59.
|
||||
keys: ["VK_TAB", "VK_TAB", "VK_END"],
|
||||
initialVal: "16:00:30",
|
||||
expectedVal: "16:00:59"
|
||||
},
|
||||
];
|
||||
|
||||
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>
|
@ -160,27 +160,6 @@ function runTest()
|
||||
'1000-12-99',
|
||||
]
|
||||
},
|
||||
{
|
||||
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: [
|
||||
|
@ -34,6 +34,33 @@ struct DecryptResult {
|
||||
RefPtr<MediaRawData> mSample;
|
||||
};
|
||||
|
||||
class CDMKeyInfo {
|
||||
public:
|
||||
explicit CDMKeyInfo(const nsTArray<uint8_t>& aKeyId)
|
||||
: mKeyId(aKeyId)
|
||||
, mStatus()
|
||||
{}
|
||||
|
||||
CDMKeyInfo(const nsTArray<uint8_t>& aKeyId,
|
||||
const dom::Optional<dom::MediaKeyStatus>& aStatus)
|
||||
: mKeyId(aKeyId)
|
||||
, mStatus(aStatus.Value())
|
||||
{}
|
||||
|
||||
// The copy-ctor and copy-assignment operator for Optional<T> are declared as
|
||||
// delete, so override CDMKeyInfo copy-ctor for nsTArray operations.
|
||||
CDMKeyInfo(const CDMKeyInfo& aKeyInfo)
|
||||
{
|
||||
mKeyId = aKeyInfo.mKeyId;
|
||||
if (aKeyInfo.mStatus.WasPassed()) {
|
||||
mStatus.Construct(aKeyInfo.mStatus.Value());
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> mKeyId;
|
||||
dom::Optional<dom::MediaKeyStatus> mStatus;
|
||||
};
|
||||
|
||||
typedef int64_t UnixTime;
|
||||
|
||||
// Proxies calls CDM, and proxies calls back.
|
||||
|
@ -41,16 +41,12 @@ public:
|
||||
uint32_t aSystemCode,
|
||||
const nsCString& aMessage) = 0;
|
||||
|
||||
virtual void KeyStatusChanged(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId,
|
||||
mozilla::dom::MediaKeyStatus aStatus) = 0;
|
||||
|
||||
virtual void ForgetKeyStatus(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId) = 0;
|
||||
|
||||
virtual void Decrypted(uint32_t aId,
|
||||
mozilla::DecryptStatus aResult,
|
||||
const nsTArray<uint8_t>& aDecryptedData) = 0;
|
||||
|
||||
virtual void BatchedKeyStatusChanged(const nsCString& aSessionId,
|
||||
const nsTArray<mozilla::CDMKeyInfo>& aKeyInfos) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -280,39 +280,26 @@ GMPCDMCallbackProxy::SessionError(const nsCString& aSessionId,
|
||||
}
|
||||
|
||||
void
|
||||
GMPCDMCallbackProxy::KeyStatusChanged(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId,
|
||||
dom::MediaKeyStatus aStatus)
|
||||
GMPCDMCallbackProxy::BatchedKeyStatusChanged(const nsCString& aSessionId,
|
||||
const nsTArray<CDMKeyInfo>& aKeyInfos)
|
||||
{
|
||||
MOZ_ASSERT(mProxy->IsOnOwnerThread());
|
||||
|
||||
KeyStatusChangedInternal(aSessionId,
|
||||
aKeyId,
|
||||
dom::Optional<dom::MediaKeyStatus>(aStatus));
|
||||
BatchedKeyStatusChangedInternal(aSessionId, aKeyInfos);
|
||||
}
|
||||
|
||||
void
|
||||
GMPCDMCallbackProxy::ForgetKeyStatus(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId)
|
||||
{
|
||||
MOZ_ASSERT(mProxy->IsOnOwnerThread());
|
||||
|
||||
KeyStatusChangedInternal(aSessionId,
|
||||
aKeyId,
|
||||
dom::Optional<dom::MediaKeyStatus>());
|
||||
}
|
||||
|
||||
void
|
||||
GMPCDMCallbackProxy::KeyStatusChangedInternal(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId,
|
||||
const dom::Optional<dom::MediaKeyStatus>& aStatus)
|
||||
GMPCDMCallbackProxy::BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
|
||||
const nsTArray<CDMKeyInfo>& aKeyInfos)
|
||||
{
|
||||
bool keyStatusesChange = false;
|
||||
{
|
||||
CDMCaps::AutoLock caps(mProxy->Capabilites());
|
||||
keyStatusesChange = caps.SetKeyStatus(aKeyId,
|
||||
NS_ConvertUTF8toUTF16(aSessionId),
|
||||
aStatus);
|
||||
for (size_t i = 0; i < aKeyInfos.Length(); i++) {
|
||||
keyStatusesChange |=
|
||||
caps.SetKeyStatus(aKeyInfos[i].mKeyId,
|
||||
NS_ConvertUTF8toUTF16(aSessionId),
|
||||
aKeyInfos[i].mStatus);
|
||||
}
|
||||
}
|
||||
if (keyStatusesChange) {
|
||||
nsCOMPtr<nsIRunnable> task;
|
||||
|
@ -43,17 +43,13 @@ public:
|
||||
uint32_t aSystemCode,
|
||||
const nsCString& aMessage) override;
|
||||
|
||||
void KeyStatusChanged(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId,
|
||||
dom::MediaKeyStatus aStatus) override;
|
||||
|
||||
void ForgetKeyStatus(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId) override;
|
||||
|
||||
void Decrypted(uint32_t aId,
|
||||
DecryptStatus aResult,
|
||||
const nsTArray<uint8_t>& aDecryptedData) override;
|
||||
|
||||
void BatchedKeyStatusChanged(const nsCString& aSessionId,
|
||||
const nsTArray<CDMKeyInfo>& aKeyInfos) override;
|
||||
|
||||
void Terminated() override;
|
||||
|
||||
~GMPCDMCallbackProxy() {}
|
||||
@ -61,10 +57,9 @@ public:
|
||||
private:
|
||||
friend class GMPCDMProxy;
|
||||
explicit GMPCDMCallbackProxy(CDMProxy* aProxy);
|
||||
void KeyStatusChangedInternal(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId,
|
||||
const dom::Optional<dom::MediaKeyStatus>& aStatus);
|
||||
|
||||
void BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
|
||||
const nsTArray<CDMKeyInfo>& aKeyInfos);
|
||||
// Warning: Weak ref.
|
||||
CDMProxy* mProxy;
|
||||
};
|
||||
|
@ -161,16 +161,29 @@ GMPDecryptorChild::KeyStatusChanged(const char* aSessionId,
|
||||
{
|
||||
AutoTArray<uint8_t, 16> kid;
|
||||
kid.AppendElements(aKeyId, aKeyIdLength);
|
||||
if (aStatus == kGMPUnknown) {
|
||||
CALL_ON_GMP_THREAD(SendForgetKeyStatus,
|
||||
nsCString(aSessionId, aSessionIdLength),
|
||||
kid);
|
||||
} else {
|
||||
CALL_ON_GMP_THREAD(SendKeyStatusChanged,
|
||||
nsCString(aSessionId, aSessionIdLength),
|
||||
kid,
|
||||
aStatus);
|
||||
|
||||
nsTArray<GMPKeyInformation> keyInfos;
|
||||
keyInfos.AppendElement(GMPKeyInformation(kid, aStatus));
|
||||
CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
|
||||
nsCString(aSessionId, aSessionIdLength),
|
||||
keyInfos);
|
||||
}
|
||||
|
||||
void
|
||||
GMPDecryptorChild::BatchedKeyStatusChanged(const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
const GMPMediaKeyInfo* aKeyInfos,
|
||||
uint32_t aKeyInfosLength)
|
||||
{
|
||||
nsTArray<GMPKeyInformation> keyInfos;
|
||||
for (uint32_t i = 0; i < aKeyInfosLength; i++) {
|
||||
nsTArray<uint8_t> keyId;
|
||||
keyId.AppendElements(aKeyInfos[i].keyid, aKeyInfos[i].keyid_size);
|
||||
keyInfos.AppendElement(GMPKeyInformation(keyId, aKeyInfos[i].status));
|
||||
}
|
||||
CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
|
||||
nsCString(aSessionId, aSessionIdLength),
|
||||
keyInfos);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -73,6 +73,11 @@ public:
|
||||
|
||||
void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) override;
|
||||
|
||||
void BatchedKeyStatusChanged(const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
const GMPMediaKeyInfo* aKeyInfos,
|
||||
uint32_t aKeyInfosLength) override;
|
||||
|
||||
// GMPDecryptorHost
|
||||
void GetSandboxVoucher(const uint8_t** aVoucher,
|
||||
uint32_t* aVoucherLength) override;
|
||||
|
@ -358,28 +358,27 @@ ToMediaKeyStatus(GMPMediaKeyStatus aStatus) {
|
||||
}
|
||||
|
||||
bool
|
||||
GMPDecryptorParent::RecvKeyStatusChanged(const nsCString& aSessionId,
|
||||
InfallibleTArray<uint8_t>&& aKeyId,
|
||||
const GMPMediaKeyStatus& aStatus)
|
||||
GMPDecryptorParent::RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
|
||||
InfallibleTArray<GMPKeyInformation>&& aKeyInfos)
|
||||
{
|
||||
LOGD(("GMPDecryptorParent[%p]::RecvKeyStatusChanged(sessionId='%s', keyId=%s, status=%d)",
|
||||
this, aSessionId.get(), ToBase64(aKeyId).get(), aStatus));
|
||||
LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(sessionId='%s', KeyInfos len='%d')",
|
||||
this, aSessionId.get(), aKeyInfos.Length()));
|
||||
|
||||
if (mIsOpen) {
|
||||
mCallback->KeyStatusChanged(aSessionId, aKeyId, ToMediaKeyStatus(aStatus));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPDecryptorParent::RecvForgetKeyStatus(const nsCString& aSessionId,
|
||||
InfallibleTArray<uint8_t>&& aKeyId)
|
||||
{
|
||||
LOGD(("GMPDecryptorParent[%p]::RecvForgetKeyStatus(sessionId='%s', keyId=%s)",
|
||||
this, aSessionId.get(), ToBase64(aKeyId).get()));
|
||||
|
||||
if (mIsOpen) {
|
||||
mCallback->ForgetKeyStatus(aSessionId, aKeyId);
|
||||
nsTArray<CDMKeyInfo> cdmKeyInfos(aKeyInfos.Length());
|
||||
for (uint32_t i = 0; i < aKeyInfos.Length(); i++) {
|
||||
LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(keyId=%s, gmp-status=%d)",
|
||||
this, ToBase64(aKeyInfos[i].keyId()).get(), aKeyInfos[i].status()));
|
||||
// If the status is kGMPUnknown, we're going to forget(remove) that key info.
|
||||
if (aKeyInfos[i].status() != kGMPUnknown) {
|
||||
auto status = ToMediaKeyStatus(aKeyInfos[i].status());
|
||||
cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId(),
|
||||
dom::Optional<dom::MediaKeyStatus>(status)));
|
||||
} else {
|
||||
cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId()));
|
||||
}
|
||||
}
|
||||
mCallback->BatchedKeyStatusChanged(aSessionId, cdmKeyInfos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -98,17 +98,13 @@ private:
|
||||
const uint32_t& aSystemCode,
|
||||
const nsCString& aMessage) override;
|
||||
|
||||
bool RecvKeyStatusChanged(const nsCString& aSessionId,
|
||||
InfallibleTArray<uint8_t>&& aKeyId,
|
||||
const GMPMediaKeyStatus& aStatus) override;
|
||||
|
||||
bool RecvForgetKeyStatus(const nsCString& aSessionId,
|
||||
InfallibleTArray<uint8_t>&& aKeyId) override;
|
||||
|
||||
bool RecvDecrypted(const uint32_t& aId,
|
||||
const GMPErr& aErr,
|
||||
InfallibleTArray<uint8_t>&& aBuffer) override;
|
||||
|
||||
bool RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
|
||||
InfallibleTArray<GMPKeyInformation>&& aKeyInfos) override;
|
||||
|
||||
bool RecvShutdown() override;
|
||||
|
||||
void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
using GMPBufferType from "gmp-video-codec.h";
|
||||
using GMPAudioCodecType from "gmp-audio-codec.h";
|
||||
using GMPMediaKeyStatus from "gmp-decryption.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace gmp {
|
||||
@ -76,5 +77,10 @@ struct GMPAudioDecodedSampleData
|
||||
uint32_t mSamplesPerSecond;
|
||||
};
|
||||
|
||||
struct GMPKeyInformation {
|
||||
uint8_t[] keyId;
|
||||
GMPMediaKeyStatus status;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ include protocol PGMPContent;
|
||||
include GMPTypes;
|
||||
|
||||
using GMPSessionMessageType from "gmp-decryption.h";
|
||||
using GMPMediaKeyStatus from "gmp-decryption.h";
|
||||
using GMPSessionType from "gmp-decryption.h";
|
||||
using GMPDOMException from "gmp-decryption.h";
|
||||
using GMPErr from "gmp-errors.h";
|
||||
@ -79,14 +78,12 @@ parent:
|
||||
uint32_t aSystemCode,
|
||||
nsCString aMessage);
|
||||
|
||||
async KeyStatusChanged(nsCString aSessionId, uint8_t[] aKey,
|
||||
GMPMediaKeyStatus aStatus);
|
||||
|
||||
async ForgetKeyStatus(nsCString aSessionId, uint8_t[] aKey);
|
||||
|
||||
async Decrypted(uint32_t aId, GMPErr aResult, uint8_t[] aBuffer);
|
||||
|
||||
async Shutdown();
|
||||
|
||||
async BatchedKeyStatusChanged(nsCString aSessionId,
|
||||
GMPKeyInformation[] aKeyInfos);
|
||||
};
|
||||
|
||||
} // namespace gmp
|
||||
|
@ -102,6 +102,20 @@ enum GMPMediaKeyStatus {
|
||||
kGMPMediaKeyStatusInvalid = 8 // Must always be last.
|
||||
};
|
||||
|
||||
struct GMPMediaKeyInfo {
|
||||
GMPMediaKeyInfo() {}
|
||||
GMPMediaKeyInfo(const uint8_t* aKeyId,
|
||||
uint32_t aKeyIdSize,
|
||||
GMPMediaKeyStatus aStatus)
|
||||
: keyid(aKeyId)
|
||||
, keyid_size(aKeyIdSize)
|
||||
, status(aStatus)
|
||||
{}
|
||||
const uint8_t* keyid;
|
||||
uint32_t keyid_size;
|
||||
GMPMediaKeyStatus status;
|
||||
};
|
||||
|
||||
// Time in milliseconds, as offset from epoch, 1 Jan 1970.
|
||||
typedef int64_t GMPTimestamp;
|
||||
|
||||
@ -192,6 +206,12 @@ public:
|
||||
// Returns decrypted buffer to Gecko, or reports failure.
|
||||
virtual void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) = 0;
|
||||
|
||||
// To aggregate KeyStatusChanged into single callback per session id.
|
||||
virtual void BatchedKeyStatusChanged(const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
const GMPMediaKeyInfo* aKeyInfos,
|
||||
uint32_t aKeyInfosLength) = 0;
|
||||
|
||||
virtual ~GMPDecryptorCallback() {}
|
||||
};
|
||||
|
||||
|
@ -409,13 +409,15 @@ WidevineDecryptor::OnSessionKeysChange(const char* aSessionId,
|
||||
return;
|
||||
}
|
||||
Log("Decryptor::OnSessionKeysChange()");
|
||||
|
||||
nsTArray<GMPMediaKeyInfo> key_infos;
|
||||
for (uint32_t i = 0; i < aKeysInfoCount; i++) {
|
||||
mCallback->KeyStatusChanged(aSessionId,
|
||||
aSessionIdSize,
|
||||
aKeysInfo[i].key_id,
|
||||
aKeysInfo[i].key_id_size,
|
||||
ToGMPKeyStatus(aKeysInfo[i].status));
|
||||
key_infos.AppendElement(GMPMediaKeyInfo(aKeysInfo[i].key_id,
|
||||
aKeysInfo[i].key_id_size,
|
||||
ToGMPKeyStatus(aKeysInfo[i].status)));
|
||||
}
|
||||
mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdSize,
|
||||
key_infos.Elements(), key_infos.Length());
|
||||
}
|
||||
|
||||
static GMPTimestamp
|
||||
|
@ -1388,16 +1388,13 @@ class GMPStorageTest : public GMPDecryptorProxyCallback
|
||||
nsresult aException,
|
||||
uint32_t aSystemCode,
|
||||
const nsCString& aMessage) override {}
|
||||
void KeyStatusChanged(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId,
|
||||
mozilla::dom::MediaKeyStatus aStatus) override { }
|
||||
|
||||
void ForgetKeyStatus(const nsCString& aSessionId,
|
||||
const nsTArray<uint8_t>& aKeyId) override { }
|
||||
|
||||
void Decrypted(uint32_t aId,
|
||||
mozilla::DecryptStatus aResult,
|
||||
const nsTArray<uint8_t>& aDecryptedData) override { }
|
||||
|
||||
void BatchedKeyStatusChanged(const nsCString& aSessionId,
|
||||
const nsTArray<CDMKeyInfo>& aKeyInfos) override { }
|
||||
|
||||
void Terminated() override {
|
||||
if (mDecryptor) {
|
||||
mDecryptor->Close();
|
||||
|
@ -298,6 +298,10 @@ MediaEngineWebRTCMicrophoneSource::UpdateSingleSource(
|
||||
LOG(("Audio engine is not initalized"));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
} else {
|
||||
// Until we fix (or wallpaper) support for multiple mic input
|
||||
// (Bug 1238038) fail allocation for a second device
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (!AllocChannel()) {
|
||||
LOG(("Audio device is not initalized"));
|
||||
|
@ -196,7 +196,7 @@ partial interface HTMLInputElement {
|
||||
// This is similar to set .value on nsIDOMInput/TextAreaElements, but handling
|
||||
// of the value change is closer to the normal user input, so 'change' event
|
||||
// for example will be dispatched when focusing out the element.
|
||||
[ChromeOnly]
|
||||
[Func="IsChromeOrXBL", NeedsSubjectPrincipal]
|
||||
void setUserInput(DOMString input);
|
||||
};
|
||||
|
||||
@ -234,3 +234,28 @@ partial interface HTMLInputElement {
|
||||
[Pref="dom.webkitBlink.dirPicker.enabled", BinaryName="WebkitDirectoryAttr", SetterThrows]
|
||||
attribute boolean webkitdirectory;
|
||||
};
|
||||
|
||||
dictionary DateTimeValue {
|
||||
long hour;
|
||||
long minute;
|
||||
};
|
||||
|
||||
partial interface HTMLInputElement {
|
||||
[Pref="dom.forms.datetime", ChromeOnly]
|
||||
DateTimeValue getDateTimeInputBoxValue();
|
||||
|
||||
[Pref="dom.forms.datetime", ChromeOnly]
|
||||
void updateDateTimeInputBox(optional DateTimeValue value);
|
||||
|
||||
[Pref="dom.forms.datetime", ChromeOnly]
|
||||
void setDateTimePickerState(boolean open);
|
||||
|
||||
[Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
|
||||
void openDateTimePicker(optional DateTimeValue initialValue);
|
||||
|
||||
[Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
|
||||
void updateDateTimePicker(optional DateTimeValue value);
|
||||
|
||||
[Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
|
||||
void closeDateTimePicker();
|
||||
};
|
||||
|
@ -861,7 +861,8 @@ nsXULElement::BindToTree(nsIDocument* aDocument,
|
||||
// Note that add-ons may introduce bindings that cause this assertion to
|
||||
// fire.
|
||||
NS_ASSERTION(IsInVideoControls(this) ||
|
||||
IsInFeedSubscribeLine(this),
|
||||
IsInFeedSubscribeLine(this) ||
|
||||
IsXULElement(nsGkAtoms::datetimebox),
|
||||
"Unexpected XUL element in non-XUL doc");
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
52343
|
||||
52346
|
||||
0/nm
|
||||
0th/pt
|
||||
1/n1
|
||||
@ -18236,6 +18236,8 @@ canoeist/SM
|
||||
canola/M
|
||||
canon/MS
|
||||
canonical/Y
|
||||
canonicalization/MS
|
||||
canonicalize/DGS
|
||||
canonization/SM
|
||||
canonize/DSG
|
||||
canoodle/DSG
|
||||
@ -37484,6 +37486,7 @@ outlast/DSG
|
||||
outlaw/SGMD
|
||||
outlay/SGM
|
||||
outlet/SM
|
||||
outlier/SM
|
||||
outline/MGDS
|
||||
outlive/GDS
|
||||
outlook/MS
|
||||
|
@ -63,6 +63,8 @@ GPUProcessManager::GPUProcessManager()
|
||||
mProcess(nullptr),
|
||||
mGPUChild(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(GPUProcessManager);
|
||||
|
||||
mObserver = new Observer(this);
|
||||
nsContentUtils::RegisterShutdownObserver(mObserver);
|
||||
|
||||
@ -71,6 +73,8 @@ GPUProcessManager::GPUProcessManager()
|
||||
|
||||
GPUProcessManager::~GPUProcessManager()
|
||||
{
|
||||
MOZ_COUNT_DTOR(GPUProcessManager);
|
||||
|
||||
LayerTreeOwnerTracker::Shutdown();
|
||||
|
||||
// The GPU process should have already been shut down.
|
||||
@ -372,14 +376,8 @@ GPUProcessManager::NotifyRemoteActorDestroyed(const uint64_t& aProcessToken)
|
||||
void
|
||||
GPUProcessManager::CleanShutdown()
|
||||
{
|
||||
if (!mProcess) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef NS_FREE_PERMANENT_DATA
|
||||
mVsyncBridge->Close();
|
||||
#endif
|
||||
DestroyProcess();
|
||||
mVsyncIOThread = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -11,15 +11,22 @@ namespace gfx {
|
||||
|
||||
VsyncIOThreadHolder::VsyncIOThreadHolder()
|
||||
{
|
||||
MOZ_COUNT_CTOR(VsyncIOThreadHolder);
|
||||
}
|
||||
|
||||
VsyncIOThreadHolder::~VsyncIOThreadHolder()
|
||||
{
|
||||
MOZ_COUNT_DTOR(VsyncIOThreadHolder);
|
||||
|
||||
if (!mThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
NS_DispatchToMainThread(NewRunnableMethod(mThread, &nsIThread::AsyncShutdown));
|
||||
if (NS_IsMainThread()) {
|
||||
mThread->AsyncShutdown();
|
||||
} else {
|
||||
NS_DispatchToMainThread(NewRunnableMethod(mThread, &nsIThread::AsyncShutdown));
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -731,7 +731,10 @@ BufferTextureHost::PrepareTextureSource(CompositableTextureSourceRef& aTexture)
|
||||
}
|
||||
|
||||
// We don't own it, apparently.
|
||||
mFirstSource = nullptr;
|
||||
if (mFirstSource) {
|
||||
mNeedsFullUpdate = true;
|
||||
mFirstSource = nullptr;
|
||||
}
|
||||
|
||||
DataTextureSource* texture = aTexture.get() ? aTexture->AsDataTextureSource() : nullptr;
|
||||
|
||||
|
@ -3654,8 +3654,12 @@ nsCSSFrameConstructor::FindInputData(Element* aElement,
|
||||
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),
|
||||
// TODO: this is temporary until a frame is written: bug 888320
|
||||
#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),
|
||||
#else
|
||||
SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewDateTimeControlFrame),
|
||||
#endif
|
||||
// TODO: this is temporary until a frame is written: bug 888320
|
||||
SIMPLE_INT_CREATE(NS_FORM_INPUT_MONTH, NS_NewTextControlFrame),
|
||||
// TODO: this is temporary until a frame is written: bug 888320
|
||||
|
@ -22,6 +22,7 @@ UNIFIED_SOURCES += [
|
||||
'nsButtonFrameRenderer.cpp',
|
||||
'nsColorControlFrame.cpp',
|
||||
'nsComboboxControlFrame.cpp',
|
||||
'nsDateTimeControlFrame.cpp',
|
||||
'nsFieldSetFrame.cpp',
|
||||
'nsFileControlFrame.cpp',
|
||||
'nsFormControlFrame.cpp',
|
||||
|
414
layout/forms/nsDateTimeControlFrame.cpp
Normal file
414
layout/forms/nsDateTimeControlFrame.cpp
Normal file
@ -0,0 +1,414 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* This frame type is used for input type=date, time, month, week, and
|
||||
* datetime-local.
|
||||
*/
|
||||
|
||||
#include "nsDateTimeControlFrame.h"
|
||||
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsFormControlFrame.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsContentCreatorFunctions.h"
|
||||
#include "nsContentList.h"
|
||||
#include "mozilla/dom/HTMLInputElement.h"
|
||||
#include "nsNodeInfoManager.h"
|
||||
#include "nsIDateTimeInputArea.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIDOMHTMLInputElement.h"
|
||||
#include "nsIDOMMutationEvent.h"
|
||||
#include "jsapi.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
nsIFrame*
|
||||
NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
|
||||
{
|
||||
return new (aPresShell) nsDateTimeControlFrame(aContext);
|
||||
}
|
||||
|
||||
NS_IMPL_FRAMEARENA_HELPERS(nsDateTimeControlFrame)
|
||||
|
||||
NS_QUERYFRAME_HEAD(nsDateTimeControlFrame)
|
||||
NS_QUERYFRAME_ENTRY(nsDateTimeControlFrame)
|
||||
NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
|
||||
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
|
||||
|
||||
nsDateTimeControlFrame::nsDateTimeControlFrame(nsStyleContext* aContext)
|
||||
: nsContainerFrame(aContext)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
|
||||
{
|
||||
nsContentUtils::DestroyAnonymousContent(&mInputAreaContent);
|
||||
nsContainerFrame::DestroyFrom(aDestructRoot);
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::UpdateInputBoxValue()
|
||||
{
|
||||
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||
do_QueryInterface(mInputAreaContent);
|
||||
if (inputAreaContent) {
|
||||
inputAreaContent->NotifyInputElementValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::SetValueFromPicker(const DateTimeValue& aValue)
|
||||
{
|
||||
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||
do_QueryInterface(mInputAreaContent);
|
||||
if (inputAreaContent) {
|
||||
AutoJSAPI api;
|
||||
if (!api.Init(mContent->OwnerDoc()->GetScopeObject())) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSObject* wrapper = mContent->GetWrapper();
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSObject* scope = xpc::GetXBLScope(api.cx(), wrapper);
|
||||
AutoJSAPI jsapi;
|
||||
if (!scope || !jsapi.Init(scope)) {
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> jsValue(jsapi.cx());
|
||||
if (!ToJSValue(jsapi.cx(), aValue, &jsValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputAreaContent->SetValueFromPicker(jsValue);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::SetPickerState(bool aOpen)
|
||||
{
|
||||
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||
do_QueryInterface(mInputAreaContent);
|
||||
if (inputAreaContent) {
|
||||
inputAreaContent->SetPickerState(aOpen);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::HandleFocusEvent()
|
||||
{
|
||||
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||
do_QueryInterface(mInputAreaContent);
|
||||
if (inputAreaContent) {
|
||||
inputAreaContent->FocusInnerTextBox();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::HandleBlurEvent()
|
||||
{
|
||||
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||
do_QueryInterface(mInputAreaContent);
|
||||
if (inputAreaContent) {
|
||||
inputAreaContent->BlurInnerTextBox();
|
||||
}
|
||||
}
|
||||
|
||||
nscoord
|
||||
nsDateTimeControlFrame::GetMinISize(nsRenderingContext* aRenderingContext)
|
||||
{
|
||||
nscoord result;
|
||||
DISPLAY_MIN_WIDTH(this, result);
|
||||
|
||||
nsIFrame* kid = mFrames.FirstChild();
|
||||
if (kid) { // display:none?
|
||||
result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
|
||||
kid,
|
||||
nsLayoutUtils::MIN_ISIZE);
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
nscoord
|
||||
nsDateTimeControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
|
||||
{
|
||||
nscoord result;
|
||||
DISPLAY_PREF_WIDTH(this, result);
|
||||
|
||||
nsIFrame* kid = mFrames.FirstChild();
|
||||
if (kid) { // display:none?
|
||||
result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
|
||||
kid,
|
||||
nsLayoutUtils::PREF_ISIZE);
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::Reflow(nsPresContext* aPresContext,
|
||||
ReflowOutput& aDesiredSize,
|
||||
const ReflowInput& aReflowInput,
|
||||
nsReflowStatus& aStatus)
|
||||
{
|
||||
MarkInReflow();
|
||||
|
||||
DO_GLOBAL_REFLOW_COUNT("nsDateTimeControlFrame");
|
||||
DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
|
||||
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
|
||||
("enter nsDateTimeControlFrame::Reflow: availSize=%d,%d",
|
||||
aReflowInput.AvailableWidth(),
|
||||
aReflowInput.AvailableHeight()));
|
||||
|
||||
NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
|
||||
|
||||
const WritingMode myWM = aReflowInput.GetWritingMode();
|
||||
|
||||
// The ISize of our content box, which is the available ISize
|
||||
// for our anonymous content:
|
||||
const nscoord contentBoxISize = aReflowInput.ComputedISize();
|
||||
nscoord contentBoxBSize = aReflowInput.ComputedBSize();
|
||||
|
||||
// Figure out our border-box sizes as well (by adding borderPadding to
|
||||
// content-box sizes):
|
||||
const nscoord borderBoxISize = contentBoxISize +
|
||||
aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM);
|
||||
|
||||
nscoord borderBoxBSize;
|
||||
if (contentBoxBSize != NS_INTRINSICSIZE) {
|
||||
borderBoxBSize = contentBoxBSize +
|
||||
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
|
||||
} // else, we'll figure out borderBoxBSize after we resolve contentBoxBSize.
|
||||
|
||||
nsIFrame* inputAreaFrame = mFrames.FirstChild();
|
||||
if (!inputAreaFrame) { // display:none?
|
||||
if (contentBoxBSize == NS_INTRINSICSIZE) {
|
||||
contentBoxBSize = 0;
|
||||
borderBoxBSize =
|
||||
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
|
||||
}
|
||||
} else {
|
||||
NS_ASSERTION(inputAreaFrame->GetContent() == mInputAreaContent,
|
||||
"What is this child doing here?");
|
||||
|
||||
ReflowOutput childDesiredSize(aReflowInput);
|
||||
|
||||
WritingMode wm = inputAreaFrame->GetWritingMode();
|
||||
LogicalSize availSize = aReflowInput.ComputedSize(wm);
|
||||
availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
|
||||
|
||||
ReflowInput childReflowOuput(aPresContext, aReflowInput,
|
||||
inputAreaFrame, availSize);
|
||||
|
||||
// Convert input area margin into my own writing-mode (in case it differs):
|
||||
LogicalMargin childMargin =
|
||||
childReflowOuput.ComputedLogicalMargin().ConvertTo(myWM, wm);
|
||||
|
||||
// offsets of input area frame within this frame:
|
||||
LogicalPoint
|
||||
childOffset(myWM,
|
||||
aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) +
|
||||
childMargin.IStart(myWM),
|
||||
aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) +
|
||||
childMargin.BStart(myWM));
|
||||
|
||||
nsReflowStatus childStatus;
|
||||
// We initially reflow the child with a dummy containerSize; positioning
|
||||
// will be fixed later.
|
||||
const nsSize dummyContainerSize;
|
||||
ReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
|
||||
childReflowOuput, myWM, childOffset, dummyContainerSize, 0,
|
||||
childStatus);
|
||||
MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus),
|
||||
"We gave our child unconstrained available block-size, "
|
||||
"so it should be complete");
|
||||
|
||||
nscoord childMarginBoxBSize =
|
||||
childDesiredSize.BSize(myWM) + childMargin.BStartEnd(myWM);
|
||||
|
||||
if (contentBoxBSize == NS_INTRINSICSIZE) {
|
||||
// We are intrinsically sized -- we should shrinkwrap the input area's
|
||||
// block-size:
|
||||
contentBoxBSize = childMarginBoxBSize;
|
||||
|
||||
// Make sure we obey min/max-bsize in the case when we're doing intrinsic
|
||||
// sizing (we get it for free when we have a non-intrinsic
|
||||
// aReflowInput.ComputedBSize()). Note that we do this before
|
||||
// adjusting for borderpadding, since ComputedMaxBSize and
|
||||
// ComputedMinBSize are content heights.
|
||||
contentBoxBSize =
|
||||
NS_CSS_MINMAX(contentBoxBSize,
|
||||
aReflowInput.ComputedMinBSize(),
|
||||
aReflowInput.ComputedMaxBSize());
|
||||
|
||||
borderBoxBSize = contentBoxBSize +
|
||||
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
|
||||
}
|
||||
|
||||
// Center child in block axis
|
||||
nscoord extraSpace = contentBoxBSize - childMarginBoxBSize;
|
||||
childOffset.B(myWM) += std::max(0, extraSpace / 2);
|
||||
|
||||
// Needed in FinishReflowChild, for logical-to-physical conversion:
|
||||
nsSize borderBoxSize = LogicalSize(myWM, borderBoxISize, borderBoxBSize).
|
||||
GetPhysicalSize(myWM);
|
||||
|
||||
// Place the child
|
||||
FinishReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
|
||||
&childReflowOuput, myWM, childOffset, borderBoxSize, 0);
|
||||
|
||||
nsSize contentBoxSize =
|
||||
LogicalSize(myWM, contentBoxISize, contentBoxBSize).
|
||||
GetPhysicalSize(myWM);
|
||||
aDesiredSize.SetBlockStartAscent(
|
||||
childDesiredSize.BlockStartAscent() +
|
||||
inputAreaFrame->BStart(aReflowInput.GetWritingMode(),
|
||||
contentBoxSize));
|
||||
}
|
||||
|
||||
LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
|
||||
aDesiredSize.SetSize(myWM, logicalDesiredSize);
|
||||
|
||||
aDesiredSize.SetOverflowAreasToDesiredBounds();
|
||||
|
||||
if (inputAreaFrame) {
|
||||
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inputAreaFrame);
|
||||
}
|
||||
|
||||
FinishAndStoreOverflow(&aDesiredSize);
|
||||
|
||||
aStatus = NS_FRAME_COMPLETE;
|
||||
|
||||
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
|
||||
("exit nsDateTimeControlFrame::Reflow: size=%d,%d",
|
||||
aDesiredSize.Width(), aDesiredSize.Height()));
|
||||
NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDateTimeControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
|
||||
{
|
||||
// Set up "datetimebox" XUL element which will be XBL-bound to the
|
||||
// actual controls.
|
||||
nsNodeInfoManager* nodeInfoManager =
|
||||
mContent->GetComposedDoc()->NodeInfoManager();
|
||||
RefPtr<NodeInfo> nodeInfo =
|
||||
nodeInfoManager->GetNodeInfo(nsGkAtoms::datetimebox, nullptr,
|
||||
kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE);
|
||||
NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
NS_TrustedNewXULElement(getter_AddRefs(mInputAreaContent), nodeInfo.forget());
|
||||
aElements.AppendElement(mInputAreaContent);
|
||||
|
||||
// Propogate our tabindex.
|
||||
nsAutoString tabIndexStr;
|
||||
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr)) {
|
||||
mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
|
||||
tabIndexStr, false);
|
||||
}
|
||||
|
||||
// Propagate our readonly state.
|
||||
nsAutoString readonly;
|
||||
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) {
|
||||
mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly,
|
||||
false);
|
||||
}
|
||||
|
||||
SyncDisabledState();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
|
||||
uint32_t aFilter)
|
||||
{
|
||||
if (mInputAreaContent) {
|
||||
aElements.AppendElement(mInputAreaContent);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::SyncDisabledState()
|
||||
{
|
||||
EventStates eventStates = mContent->AsElement()->State();
|
||||
if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
|
||||
mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
|
||||
EmptyString(), true);
|
||||
} else {
|
||||
mInputAreaContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
|
||||
nsIAtom* aAttribute,
|
||||
int32_t aModType)
|
||||
{
|
||||
NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
|
||||
|
||||
// nsGkAtoms::disabled is handled by SyncDisabledState
|
||||
if (aNameSpaceID == kNameSpaceID_None) {
|
||||
if (aAttribute == nsGkAtoms::value ||
|
||||
aAttribute == nsGkAtoms::readonly ||
|
||||
aAttribute == nsGkAtoms::tabindex) {
|
||||
MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
|
||||
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 (aAttribute == nsGkAtoms::value) {
|
||||
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||
do_QueryInterface(mInputAreaContent);
|
||||
if (inputAreaContent) {
|
||||
nsContentUtils::AddScriptRunner(NewRunnableMethod(inputAreaContent,
|
||||
&nsIDateTimeInputArea::NotifyInputElementValueChanged));
|
||||
}
|
||||
} else {
|
||||
if (aModType == nsIDOMMutationEvent::REMOVAL) {
|
||||
mInputAreaContent->UnsetAttr(aNameSpaceID, aAttribute, true);
|
||||
} else {
|
||||
MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
|
||||
aModType == nsIDOMMutationEvent::MODIFICATION);
|
||||
nsAutoString value;
|
||||
mContent->GetAttr(aNameSpaceID, aAttribute, value);
|
||||
mInputAreaContent->SetAttr(aNameSpaceID, aAttribute, value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
|
||||
aModType);
|
||||
}
|
||||
|
||||
void
|
||||
nsDateTimeControlFrame::ContentStatesChanged(EventStates aStates)
|
||||
{
|
||||
if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
|
||||
nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
|
||||
}
|
||||
}
|
||||
|
||||
nsIAtom*
|
||||
nsDateTimeControlFrame::GetType() const
|
||||
{
|
||||
return nsGkAtoms::dateTimeControlFrame;
|
||||
}
|
119
layout/forms/nsDateTimeControlFrame.h
Normal file
119
layout/forms/nsDateTimeControlFrame.h
Normal file
@ -0,0 +1,119 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* This frame type is used for input type=date, time, month, week, and
|
||||
* datetime-local.
|
||||
*
|
||||
* NOTE: some of the above-mentioned input types are still to-be-implemented.
|
||||
* See nsCSSFrameConstructor::FindInputData, as well as bug 1286182 (date),
|
||||
* bug 1306215 (month), bug 1306216 (week) and bug 1306217 (datetime-local).
|
||||
*/
|
||||
|
||||
#ifndef nsDateTimeControlFrame_h__
|
||||
#define nsDateTimeControlFrame_h__
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "nsContainerFrame.h"
|
||||
#include "nsIAnonymousContentCreator.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
struct DateTimeValue;
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
class nsDateTimeControlFrame final : public nsContainerFrame,
|
||||
public nsIAnonymousContentCreator
|
||||
{
|
||||
typedef mozilla::dom::DateTimeValue DateTimeValue;
|
||||
|
||||
explicit nsDateTimeControlFrame(nsStyleContext* aContext);
|
||||
|
||||
public:
|
||||
friend nsIFrame* NS_NewDateTimeControlFrame(nsIPresShell* aPresShell,
|
||||
nsStyleContext* aContext);
|
||||
|
||||
void ContentStatesChanged(mozilla::EventStates aStates) override;
|
||||
void DestroyFrom(nsIFrame* aDestructRoot) override;
|
||||
|
||||
NS_DECL_QUERYFRAME_TARGET(nsDateTimeControlFrame)
|
||||
NS_DECL_QUERYFRAME
|
||||
NS_DECL_FRAMEARENA_HELPERS
|
||||
|
||||
#ifdef DEBUG_FRAME_DUMP
|
||||
nsresult GetFrameName(nsAString& aResult) const override {
|
||||
return MakeFrameName(NS_LITERAL_STRING("DateTimeControl"), aResult);
|
||||
}
|
||||
#endif
|
||||
|
||||
nsIAtom* GetType() const override;
|
||||
|
||||
bool IsFrameOfType(uint32_t aFlags) const override
|
||||
{
|
||||
return nsContainerFrame::IsFrameOfType(aFlags &
|
||||
~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
|
||||
}
|
||||
|
||||
// Reflow
|
||||
nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
|
||||
|
||||
nscoord GetPrefISize(nsRenderingContext* aRenderingContext) override;
|
||||
|
||||
void Reflow(nsPresContext* aPresContext,
|
||||
ReflowOutput& aDesiredSize,
|
||||
const ReflowInput& aReflowState,
|
||||
nsReflowStatus& aStatus) override;
|
||||
|
||||
// nsIAnonymousContentCreator
|
||||
nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
|
||||
void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
|
||||
uint32_t aFilter) override;
|
||||
|
||||
nsresult AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute,
|
||||
int32_t aModType) override;
|
||||
|
||||
void UpdateInputBoxValue();
|
||||
void SetValueFromPicker(const DateTimeValue& aValue);
|
||||
void HandleFocusEvent();
|
||||
void HandleBlurEvent();
|
||||
void SetPickerState(bool aOpen);
|
||||
|
||||
private:
|
||||
class SyncDisabledStateEvent;
|
||||
friend class SyncDisabledStateEvent;
|
||||
class SyncDisabledStateEvent : public mozilla::Runnable
|
||||
{
|
||||
public:
|
||||
explicit SyncDisabledStateEvent(nsDateTimeControlFrame* aFrame)
|
||||
: mFrame(aFrame)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
nsDateTimeControlFrame* frame =
|
||||
static_cast<nsDateTimeControlFrame*>(mFrame.GetFrame());
|
||||
NS_ENSURE_STATE(frame);
|
||||
|
||||
frame->SyncDisabledState();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsWeakFrame mFrame;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync the disabled state of the anonymous children up with our content's.
|
||||
*/
|
||||
void SyncDisabledState();
|
||||
|
||||
// Anonymous child which is bound via XBL to an element that wraps the input
|
||||
// area and reset button.
|
||||
nsCOMPtr<nsIContent> mInputAreaContent;
|
||||
};
|
||||
|
||||
#endif // nsDateTimeControlFrame_h__
|
@ -65,4 +65,6 @@ skip-if = toolkit == 'android' # Bug 1021644 - Fails when pushed into a differen
|
||||
skip-if = buildapp == 'mulet' || buildapp == 'b2g'
|
||||
[test_select_vertical.html]
|
||||
skip-if = e10s || buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # Bug 1170129 - vertical <select> popup not implemented for e10s # <select> elements don't use an in-page popup on B2G/Android
|
||||
[test_bug1301290.html]
|
||||
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(resizing textarea not available in b2g) b2g-debug(resizing textarea not available in b2g) b2g-desktop(resizing textarea not available in b2g)
|
||||
[test_bug1305282.html]
|
||||
|
49
layout/forms/test/test_bug1301290.html
Normal file
49
layout/forms/test/test_bug1301290.html
Normal file
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Bug 1301290</title>
|
||||
<script type="application/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"/>
|
||||
<style type="text/css">
|
||||
.blue, .green {
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
overflow: scroll;
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.blue {
|
||||
background: blue;
|
||||
}
|
||||
|
||||
.green {
|
||||
background: green;
|
||||
margin-top: -100px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="blue"></div>
|
||||
<textarea class="green" id="textarea"></textarea>
|
||||
<script type="application/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(() => SimpleTest.executeSoon(function() {
|
||||
var textarea = $("textarea");
|
||||
var rect = textarea.getBoundingClientRect();
|
||||
|
||||
synthesizeMouse(textarea, rect.width - 10, rect.height - 10, { type: "mousedown" });
|
||||
synthesizeMouse(textarea, rect.width + 40, rect.height + 40, { type: "mousemove" });
|
||||
synthesizeMouse(textarea, rect.width + 40, rect.height + 40, { type: "mouseup" });
|
||||
|
||||
var newrect = textarea.getBoundingClientRect();
|
||||
ok(newrect.width > rect.width, "width did not increase");
|
||||
ok(newrect.height > rect.height, "height did not increase");
|
||||
SimpleTest.finish();
|
||||
}));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -19,6 +19,7 @@ FRAME_ID(nsComboboxControlFrame)
|
||||
FRAME_ID(nsComboboxDisplayFrame)
|
||||
FRAME_ID(nsContainerFrame)
|
||||
FRAME_ID(nsContinuingTextFrame)
|
||||
FRAME_ID(nsDateTimeControlFrame)
|
||||
FRAME_ID(nsDeckFrame)
|
||||
FRAME_ID(nsDocElementBoxFrame)
|
||||
FRAME_ID(nsFieldSetFrame)
|
||||
|
@ -2937,7 +2937,7 @@ ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOri
|
||||
static int32_t
|
||||
MaxZIndexInList(nsDisplayList* aList, nsDisplayListBuilder* aBuilder)
|
||||
{
|
||||
int32_t maxZIndex = 0;
|
||||
int32_t maxZIndex = -1;
|
||||
for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) {
|
||||
maxZIndex = std::max(maxZIndex, item->ZIndex());
|
||||
}
|
||||
@ -2966,6 +2966,20 @@ MaxZIndexInListOfItemsContainedInFrame(nsDisplayList* aList, nsIFrame* aFrame)
|
||||
return maxZIndex;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void
|
||||
AppendInternalItemToTop(const nsDisplayListSet& aLists,
|
||||
T* aItem,
|
||||
int32_t aZIndex)
|
||||
{
|
||||
if (aZIndex >= 0) {
|
||||
aItem->SetOverrideZIndex(aZIndex);
|
||||
aLists.PositionedDescendants()->AppendNewToTop(aItem);
|
||||
} else {
|
||||
aLists.Content()->AppendNewToTop(aItem);
|
||||
}
|
||||
}
|
||||
|
||||
static const uint32_t APPEND_OWN_LAYER = 0x1;
|
||||
static const uint32_t APPEND_POSITIONED = 0x2;
|
||||
static const uint32_t APPEND_SCROLLBAR_CONTAINER = 0x4;
|
||||
@ -2991,13 +3005,8 @@ AppendToTop(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists,
|
||||
// We want overlay scrollbars to always be on top of the scrolled content,
|
||||
// but we don't want them to unnecessarily cover overlapping elements from
|
||||
// outside our scroll frame.
|
||||
nsDisplayList* positionedDescendants = aLists.PositionedDescendants();
|
||||
if (!positionedDescendants->IsEmpty()) {
|
||||
newItem->SetOverrideZIndex(MaxZIndexInList(positionedDescendants, aBuilder));
|
||||
positionedDescendants->AppendNewToTop(newItem);
|
||||
} else {
|
||||
aLists.Outlines()->AppendNewToTop(newItem);
|
||||
}
|
||||
int32_t zIndex = MaxZIndexInList(aLists.PositionedDescendants(), aBuilder);
|
||||
AppendInternalItemToTop(aLists, newItem, zIndex);
|
||||
} else {
|
||||
aLists.BorderBackground()->AppendNewToTop(newItem);
|
||||
}
|
||||
@ -3573,17 +3582,9 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
||||
}
|
||||
|
||||
if (inactiveRegionItem) {
|
||||
nsDisplayList* positionedDescendants = scrolledContent.PositionedDescendants();
|
||||
nsDisplayList* destinationList = nullptr;
|
||||
int32_t zindex =
|
||||
MaxZIndexInListOfItemsContainedInFrame(positionedDescendants, mOuter);
|
||||
if (zindex >= 0) {
|
||||
destinationList = positionedDescendants;
|
||||
inactiveRegionItem->SetOverrideZIndex(zindex);
|
||||
} else {
|
||||
destinationList = scrolledContent.Outlines();
|
||||
}
|
||||
destinationList->AppendNewToTop(inactiveRegionItem);
|
||||
int32_t zIndex =
|
||||
MaxZIndexInListOfItemsContainedInFrame(scrolledContent.PositionedDescendants(), mOuter);
|
||||
AppendInternalItemToTop(scrolledContent, inactiveRegionItem, zIndex);
|
||||
}
|
||||
|
||||
if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
|
||||
|
@ -171,6 +171,8 @@ nsIFrame*
|
||||
NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||
nsIFrame*
|
||||
NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||
nsIFrame*
|
||||
NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||
nsBlockFrame*
|
||||
NS_NewDetailsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="checkbox" style="-moz-appearance:none;">
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<!-- Test: when switching to another type, the input element should look
|
||||
like that type (not like an input time element) -->
|
||||
<script type="text/javascript">
|
||||
function setToCheckbox()
|
||||
{
|
||||
document.getElementById("i").type = "checkbox";
|
||||
document.documentElement.className = "";
|
||||
}
|
||||
document.addEventListener("MozReftestInvalidate", setToCheckbox);
|
||||
</script>
|
||||
<body>
|
||||
<input type="time" id="i" style="-moz-appearance:none;">
|
||||
</body>
|
||||
</html>
|
13
layout/reftests/forms/input/datetime/reftest.list
Normal file
13
layout/reftests/forms/input/datetime/reftest.list
Normal file
@ -0,0 +1,13 @@
|
||||
default-preferences pref(dom.forms.datetime,true)
|
||||
|
||||
# not valid on Android/B2G where type=time looks like type=text
|
||||
skip-if(Android||B2G||Mulet) != time-simple-unthemed.html time-simple-unthemed-ref.html
|
||||
skip-if(Android||B2G||Mulet) != time-large-font.html time-basic.html
|
||||
skip-if(Android||B2G||Mulet) != time-width-height.html time-basic.html
|
||||
skip-if(Android||B2G||Mulet) != time-border.html time-basic.html
|
||||
# only valid on Android/B2G where type=number looks the same as type=text
|
||||
skip-if(!Android&&!B2G&&!Mulet) == time-simple-unthemed.html time-simple-unthemed-ref.html
|
||||
|
||||
# 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
|
6
layout/reftests/forms/input/datetime/time-basic.html
Normal file
6
layout/reftests/forms/input/datetime/time-basic.html
Normal file
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="time" value="12:30">
|
||||
</body>
|
||||
</html>
|
6
layout/reftests/forms/input/datetime/time-border.html
Normal file
6
layout/reftests/forms/input/datetime/time-border.html
Normal file
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="time" value="12:30" style="border:10px solid blue">
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="time" value="12:30" style="font-size: 32px;">
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="text" style="-moz-appearance:none;">
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="time" style="-moz-appearance:none;">
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<input type="time" style="width:200px; height:50px">
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<!-- Test: input element changed to time state doesn't look like checkbox state -->
|
||||
<script type="text/javascript">
|
||||
function setToTime()
|
||||
{
|
||||
document.getElementById("i").type = "time";
|
||||
document.documentElement.className = "";
|
||||
}
|
||||
document.addEventListener("MozReftestInvalidate", setToTime);
|
||||
</script>
|
||||
<body>
|
||||
<input type="checkbox" id="i" style="-moz-appearance:none;">
|
||||
</body>
|
||||
</html>
|
@ -11,3 +11,4 @@ include text/reftest.list
|
||||
include percentage/reftest.list
|
||||
include hidden/reftest.list
|
||||
include color/reftest.list
|
||||
include datetime/reftest.list
|
||||
|
@ -767,6 +767,13 @@ video > .caption-box {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* datetime elements */
|
||||
|
||||
input[type="time"] > xul|datetimebox {
|
||||
display: flex;
|
||||
-moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input");
|
||||
}
|
||||
|
||||
/* details & summary */
|
||||
/* Need to revert Bug 1259889 Part 2 when removing details preference. */
|
||||
@supports -moz-bool-pref("dom.details_element.enabled") {
|
||||
|
@ -41,15 +41,14 @@ ClearKeySession::~ClearKeySession()
|
||||
{
|
||||
CK_LOGD("ClearKeySession dtor %p", this);
|
||||
|
||||
auto& keyIds = GetKeyIds();
|
||||
for (auto it = keyIds.begin(); it != keyIds.end(); it++) {
|
||||
assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(*it));
|
||||
|
||||
ClearKeyDecryptionManager::Get()->ReleaseKeyId(*it);
|
||||
mCallback->KeyStatusChanged(&mSessionId[0], mSessionId.size(),
|
||||
&(*it)[0], it->size(),
|
||||
kGMPUnknown);
|
||||
std::vector<GMPMediaKeyInfo> key_infos;
|
||||
for (const KeyId& keyId : mKeyIds) {
|
||||
assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(keyId));
|
||||
ClearKeyDecryptionManager::Get()->ReleaseKeyId(keyId);
|
||||
key_infos.push_back(GMPMediaKeyInfo(&keyId[0], keyId.size(), kGMPUnknown));
|
||||
}
|
||||
mCallback->BatchedKeyStatusChanged(&mSessionId[0], mSessionId.size(),
|
||||
key_infos.data(), key_infos.size());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -168,24 +168,33 @@ ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus,
|
||||
mSessions[aSessionId] = session;
|
||||
|
||||
uint32_t numKeys = aKeyDataSize / (2 * CLEARKEY_KEY_LEN);
|
||||
|
||||
vector<GMPMediaKeyInfo> key_infos;
|
||||
vector<KeyIdPair> keyPairs;
|
||||
for (uint32_t i = 0; i < numKeys; i ++) {
|
||||
const uint8_t* base = aKeyData + 2 * CLEARKEY_KEY_LEN * i;
|
||||
|
||||
KeyId keyId(base, base + CLEARKEY_KEY_LEN);
|
||||
assert(keyId.size() == CLEARKEY_KEY_LEN);
|
||||
KeyIdPair keyPair;
|
||||
|
||||
Key key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
|
||||
assert(key.size() == CLEARKEY_KEY_LEN);
|
||||
keyPair.mKeyId = KeyId(base, base + CLEARKEY_KEY_LEN);
|
||||
assert(keyPair.mKeyId.size() == CLEARKEY_KEY_LEN);
|
||||
|
||||
session->AddKeyId(keyId);
|
||||
keyPair.mKey = Key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
|
||||
assert(keyPair.mKey.size() == CLEARKEY_KEY_LEN);
|
||||
|
||||
mDecryptionManager->ExpectKeyId(keyId);
|
||||
mDecryptionManager->InitKey(keyId, key);
|
||||
mKeyIds.insert(key);
|
||||
mCallback->KeyStatusChanged(&aSessionId[0], aSessionId.size(),
|
||||
&keyId[0], keyId.size(),
|
||||
kGMPUsable);
|
||||
session->AddKeyId(keyPair.mKeyId);
|
||||
|
||||
mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
|
||||
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
|
||||
mKeyIds.insert(keyPair.mKey);
|
||||
|
||||
keyPairs.push_back(keyPair);
|
||||
key_infos.push_back(GMPMediaKeyInfo(&keyPairs[i].mKeyId[0],
|
||||
keyPairs[i].mKeyId.size(),
|
||||
kGMPUsable));
|
||||
}
|
||||
mCallback->BatchedKeyStatusChanged(&aSessionId[0], aSessionId.size(),
|
||||
key_infos.data(), key_infos.size());
|
||||
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, true);
|
||||
}
|
||||
@ -216,13 +225,17 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = keyPairs.begin(); it != keyPairs.end(); it++) {
|
||||
mDecryptionManager->InitKey(it->mKeyId, it->mKey);
|
||||
mKeyIds.insert(it->mKeyId);
|
||||
mCallback->KeyStatusChanged(aSessionId, aSessionIdLength,
|
||||
&it->mKeyId[0], it->mKeyId.size(),
|
||||
kGMPUsable);
|
||||
vector<GMPMediaKeyInfo> key_infos;
|
||||
for (size_t i = 0; i < keyPairs.size(); i++) {
|
||||
KeyIdPair& keyPair = keyPairs[i];
|
||||
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
|
||||
mKeyIds.insert(keyPair.mKeyId);
|
||||
key_infos.push_back(GMPMediaKeyInfo(&keyPair.mKeyId[0],
|
||||
keyPair.mKeyId.size(),
|
||||
kGMPUsable));
|
||||
}
|
||||
mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdLength,
|
||||
key_infos.data(), key_infos.size());
|
||||
|
||||
if (session->Type() != kGMPPersistentSession) {
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
|
@ -1,5 +0,0 @@
|
||||
[encrypted-media-keystatuses.html]
|
||||
type: testharness
|
||||
[Verify MediaKeySession.keyStatuses.]
|
||||
expected: FAIL
|
||||
|
@ -167,7 +167,7 @@ function setupFormHistory(aCallback) {
|
||||
{ op : "add", fieldname : "field9", value : "value" },
|
||||
{ op : "add", fieldname : "field10", value : "42" },
|
||||
{ op : "add", fieldname : "field11", value : "2010-10-10" },
|
||||
{ op : "add", fieldname : "field12", value : "21:21" },
|
||||
{ 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
|
||||
{ op : "add", fieldname : "field15", value : "2016-08" },
|
||||
@ -913,15 +913,13 @@ function runTest() {
|
||||
|
||||
input = $_(15, "field12");
|
||||
restoreForm();
|
||||
expectPopup();
|
||||
doKey("down");
|
||||
waitForMenuChange(0);
|
||||
break;
|
||||
|
||||
case 406:
|
||||
checkMenuEntries(["21:21"]);
|
||||
doKey("down");
|
||||
doKey("return");
|
||||
checkForm("21:21");
|
||||
checkMenuEntries([]); // type=time with it's own control frame does not
|
||||
// have a drop down menu for now
|
||||
checkForm("");
|
||||
|
||||
input = $_(16, "field13");
|
||||
restoreForm();
|
||||
|
@ -1509,3 +1509,151 @@ let AutoCompletePopup = {
|
||||
}
|
||||
|
||||
AutoCompletePopup.init();
|
||||
|
||||
/**
|
||||
* DateTimePickerListener is the communication channel between the input box
|
||||
* (content) for date/time input types and its picker (chrome).
|
||||
*/
|
||||
let DateTimePickerListener = {
|
||||
/**
|
||||
* On init, just listen for the event to open the picker, once the picker is
|
||||
* opened, we'll listen for update and close events.
|
||||
*/
|
||||
init: function() {
|
||||
addEventListener("MozOpenDateTimePicker", this);
|
||||
this._inputElement = null;
|
||||
|
||||
addEventListener("unload", () => {
|
||||
this.uninit();
|
||||
});
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
removeEventListener("MozOpenDateTimePicker", this);
|
||||
this._inputElement = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup function called when picker is closed.
|
||||
*/
|
||||
close: function() {
|
||||
this.removeListeners();
|
||||
this._inputElement.setDateTimePickerState(false);
|
||||
this._inputElement = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called after picker is opened to start listening for input box update
|
||||
* events.
|
||||
*/
|
||||
addListeners: function() {
|
||||
addEventListener("MozUpdateDateTimePicker", this);
|
||||
addEventListener("MozCloseDateTimePicker", this);
|
||||
addEventListener("pagehide", this);
|
||||
|
||||
addMessageListener("FormDateTime:PickerValueChanged", this);
|
||||
addMessageListener("FormDateTime:PickerClosed", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop listeneing for events when picker is closed.
|
||||
*/
|
||||
removeListeners: function() {
|
||||
removeEventListener("MozUpdateDateTimePicker", this);
|
||||
removeEventListener("MozCloseDateTimePicker", this);
|
||||
removeEventListener("pagehide", this);
|
||||
|
||||
removeMessageListener("FormDateTime:PickerValueChanged", this);
|
||||
removeMessageListener("FormDateTime:PickerClosed", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function that returns the CSS direction property of the element.
|
||||
*/
|
||||
getComputedDirection: function(aElement) {
|
||||
return aElement.ownerDocument.defaultView.getComputedStyle(aElement)
|
||||
.getPropertyValue("direction");
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function that returns the rect of the element, which is the position
|
||||
* in "screen" coordinates.
|
||||
*/
|
||||
getBoundingContentRect: function(aElement) {
|
||||
return BrowserUtils.getElementBoundingScreenRect(aElement);
|
||||
},
|
||||
|
||||
/**
|
||||
* nsIMessageListener.
|
||||
*/
|
||||
receiveMessage: function(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
case "FormDateTime:PickerClosed": {
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
case "FormDateTime:PickerValueChanged": {
|
||||
this._inputElement.updateDateTimeInputBox(aMessage.data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* nsIDOMEventListener, for chrome events sent by the input element and other
|
||||
* DOM events.
|
||||
*/
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "MozOpenDateTimePicker": {
|
||||
if (!(aEvent.originalTarget instanceof content.HTMLInputElement)) {
|
||||
return;
|
||||
}
|
||||
this._inputElement = aEvent.originalTarget;
|
||||
this._inputElement.setDateTimePickerState(true);
|
||||
this.addListeners();
|
||||
|
||||
let value = this._inputElement.getDateTimeInputBoxValue();
|
||||
sendAsyncMessage("FormDateTime:OpenPicker", {
|
||||
rect: this.getBoundingContentRect(this._inputElement),
|
||||
dir: this.getComputedDirection(this._inputElement),
|
||||
type: this._inputElement.type,
|
||||
detail: {
|
||||
// Pass partial value if it's available, otherwise pass input
|
||||
// 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,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "MozUpdateDateTimePicker": {
|
||||
let value = this._inputElement.getDateTimeInputBoxValue();
|
||||
sendAsyncMessage("FormDateTime:UpdatePicker", { value });
|
||||
break;
|
||||
}
|
||||
case "MozCloseDateTimePicker": {
|
||||
sendAsyncMessage("FormDateTime:ClosePicker");
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
case "pagehide": {
|
||||
if (this._inputElement &&
|
||||
this._inputElement.ownerDocument == aEvent.target) {
|
||||
sendAsyncMessage("FormDateTime:ClosePicker");
|
||||
this.close();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
DateTimePickerListener.init();
|
||||
|
@ -71,6 +71,8 @@ toolkit.jar:
|
||||
content/global/bindings/checkbox.xml (widgets/checkbox.xml)
|
||||
content/global/bindings/colorpicker.xml (widgets/colorpicker.xml)
|
||||
content/global/bindings/datetimepicker.xml (widgets/datetimepicker.xml)
|
||||
content/global/bindings/datetimebox.xml (widgets/datetimebox.xml)
|
||||
content/global/bindings/datetimebox.css (widgets/datetimebox.css)
|
||||
* content/global/bindings/dialog.xml (widgets/dialog.xml)
|
||||
content/global/bindings/editor.xml (widgets/editor.xml)
|
||||
content/global/bindings/expander.xml (widgets/expander.xml)
|
||||
|
@ -281,6 +281,10 @@
|
||||
onget="return document.getElementById(this.getAttribute('autocompletepopup'))"
|
||||
readonly="true"/>
|
||||
|
||||
<property name="dateTimePicker"
|
||||
onget="return document.getElementById(this.getAttribute('datetimepicker'))"
|
||||
readonly="true"/>
|
||||
|
||||
<property name="docShellIsActive">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
|
44
toolkit/content/widgets/datetimebox.css
Normal file
44
toolkit/content/widgets/datetimebox.css
Normal file
@ -0,0 +1,44 @@
|
||||
/* 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.w3.org/1999/xhtml");
|
||||
@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
.datetime-input-box-wrapper {
|
||||
-moz-appearance: none;
|
||||
display: inline-flex;
|
||||
cursor: default;
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.datetime-input {
|
||||
-moz-appearance: none;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
ime-mode: disabled;
|
||||
}
|
||||
|
||||
.datetime-separator {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.datetime-input[readonly],
|
||||
.datetime-input[disabled] {
|
||||
color: GrayText;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.datetime-reset-button {
|
||||
background-image: url(chrome://global/skin/icons/input-clear.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 12px, 12px;
|
||||
border: none;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
align-self: center;
|
||||
justify-content: flex-end;
|
||||
}
|
806
toolkit/content/widgets/datetimebox.xml
Normal file
806
toolkit/content/widgets/datetimebox.xml
Normal file
@ -0,0 +1,806 @@
|
||||
<?xml version="1.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/. -->
|
||||
|
||||
<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="time-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[
|
||||
// TODO: Bug 1301312 - localization for input type=time input.
|
||||
this.mHour12 = true;
|
||||
this.mAMIndicator = "AM";
|
||||
this.mPMIndicator = "PM";
|
||||
this.mPlaceHolder = "--";
|
||||
this.mSeparatorText = ":";
|
||||
this.mMillisecSeparatorText = ".";
|
||||
this.mMaxLength = 2;
|
||||
this.mMillisecMaxLength = 3;
|
||||
this.mDefaultStep = 60 * 1000; // in milliseconds
|
||||
|
||||
this.mMinHourInHour12 = 1;
|
||||
this.mMaxHourInHour12 = 12;
|
||||
this.mMinMinute = 0;
|
||||
this.mMaxMinute = 59;
|
||||
this.mMinSecond = 0;
|
||||
this.mMaxSecond = 59;
|
||||
this.mMinMillisecond = 0;
|
||||
this.mMaxMillisecond = 999;
|
||||
|
||||
this.mHourPageUpDownInterval = 3;
|
||||
this.mMinSecPageUpDownInterval = 10;
|
||||
|
||||
this.mHourField =
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "input-one");
|
||||
this.mHourField.setAttribute("typeBuffer", "");
|
||||
this.mMinuteField =
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "input-two");
|
||||
this.mMinuteField.setAttribute("typeBuffer", "");
|
||||
this.mDayPeriodField =
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "input-three");
|
||||
this.mDayPeriodField.classList.remove("numeric");
|
||||
|
||||
this.mHourField.placeholder = this.mPlaceHolder;
|
||||
this.mMinuteField.placeholder = this.mPlaceHolder;
|
||||
this.mDayPeriodField.placeholder = this.mPlaceHolder;
|
||||
|
||||
this.mHourField.setAttribute("min", this.mMinHourInHour12);
|
||||
this.mHourField.setAttribute("max", this.mMaxHourInHour12);
|
||||
this.mMinuteField.setAttribute("min", this.mMinMinute);
|
||||
this.mMinuteField.setAttribute("max", this.mMaxMinute);
|
||||
|
||||
this.mMinuteSeparator =
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
|
||||
this.mMinuteSeparator.textContent = this.mSeparatorText;
|
||||
this.mSpaceSeparator =
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
|
||||
// space between time and am/pm field
|
||||
this.mSpaceSeparator.textContent = " ";
|
||||
|
||||
this.mSecondSeparator = null;
|
||||
this.mSecondField = null;
|
||||
this.mMillisecSeparator = null;
|
||||
this.mMillisecField = null;
|
||||
|
||||
if (this.mInputElement.value) {
|
||||
this.setFieldsFromInputValue();
|
||||
}
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<method name="insertSeparator">
|
||||
<parameter name="aSeparatorText"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let container = this.mHourField.parentNode;
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
let separator = document.createElementNS(HTML_NS, "span");
|
||||
separator.textContent = aSeparatorText;
|
||||
separator.setAttribute("class", "datetime-separator");
|
||||
container.insertBefore(separator, this.mSpaceSeparator);
|
||||
|
||||
return separator;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="insertAdditionalField">
|
||||
<parameter name="aPlaceHolder"/>
|
||||
<parameter name="aMin"/>
|
||||
<parameter name="aMax"/>
|
||||
<parameter name="aSize"/>
|
||||
<parameter name="aMaxLength"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let container = this.mHourField.parentNode;
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
let field = document.createElementNS(HTML_NS, "input");
|
||||
field.classList.add("textbox-input", "datetime-input", "numeric");
|
||||
field.setAttribute("size", aSize);
|
||||
field.setAttribute("maxlength", aMaxLength);
|
||||
field.setAttribute("min", aMin);
|
||||
field.setAttribute("max", aMax);
|
||||
field.setAttribute("typeBuffer", "");
|
||||
field.disabled = this.mInputElement.disabled;
|
||||
field.readOnly = this.mInputElement.readOnly;
|
||||
field.tabIndex = this.mInputElement.tabIndex;
|
||||
field.placeholder = aPlaceHolder;
|
||||
container.insertBefore(field, this.mSpaceSeparator);
|
||||
|
||||
return field;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="setFieldsFromInputValue">
|
||||
<body>
|
||||
<![CDATA[
|
||||
let value = this.mInputElement.value;
|
||||
if (!value) {
|
||||
this.clearInputFields(true);
|
||||
return;
|
||||
}
|
||||
|
||||
this.log("setFieldsFromInputValue: " + value);
|
||||
let [hour, minute, second] = value.split(':');
|
||||
|
||||
this.setFieldValue(this.mHourField, hour);
|
||||
this.setFieldValue(this.mMinuteField, minute);
|
||||
if (this.mHour12) {
|
||||
this.mDayPeriodField.value = (hour >= this.mMaxHourInHour12) ?
|
||||
this.mPMIndicator : this.mAMIndicator;
|
||||
}
|
||||
|
||||
if (!this.isEmpty(second)) {
|
||||
let index = second.indexOf(".");
|
||||
let millisecond;
|
||||
if (index != -1) {
|
||||
millisecond = second.substring(index + 1);
|
||||
second = second.substring(0, index);
|
||||
}
|
||||
|
||||
if (!this.mSecondField) {
|
||||
this.mSecondSeparator = this.insertSeparator(this.mSeparatorText);
|
||||
this.mSecondField = this.insertAdditionalField(this.mPlaceHolder,
|
||||
this.mMinSecond, this.mMaxSecond, this.mMaxLength,
|
||||
this.mMaxLength);
|
||||
}
|
||||
this.setFieldValue(this.mSecondField, second);
|
||||
|
||||
if (!this.isEmpty(millisecond)) {
|
||||
if (!this.mMillisecField) {
|
||||
this.mMillisecSeparator = this.insertSeparator(
|
||||
this.mMillisecSeparatorText);
|
||||
this.mMillisecField = this.insertAdditionalField(
|
||||
this.mPlaceHolder, this.mMinMillisecond, this.mMaxMillisecond,
|
||||
this.mMillisecMaxLength, this.mMillisecMaxLength);
|
||||
}
|
||||
this.setFieldValue(this.mMillisecField, millisecond);
|
||||
} else if (this.mMillisecField) {
|
||||
this.mMillisecField.remove();
|
||||
this.mMillisecField = null;
|
||||
|
||||
this.mMillisecSeparator.remove();
|
||||
this.mMillisecSeparator = null;
|
||||
}
|
||||
} else {
|
||||
if (this.mSecondField) {
|
||||
this.mSecondField.remove();
|
||||
this.mSecondField = null;
|
||||
|
||||
this.mSecondSeparator.remove();
|
||||
this.mSecondSeparator = null;
|
||||
}
|
||||
|
||||
if (this.mMillisecField) {
|
||||
this.mMillisecField.remove();
|
||||
this.mMillisecField = null;
|
||||
|
||||
this.mMillisecSeparator.remove();
|
||||
this.mMillisecSeparator = null;
|
||||
}
|
||||
}
|
||||
this.notifyPicker();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="setInputValueFromFields">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.isEmpty(this.mHourField.value) ||
|
||||
this.isEmpty(this.mMinuteField.value) ||
|
||||
(this.mDayPeriodField && this.isEmpty(this.mDayPeriodField.value)) ||
|
||||
(this.mSecondField && this.isEmpty(this.mSecondField.value))) {
|
||||
// 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 hour = Number(this.mHourField.value);
|
||||
if (this.mHour12) {
|
||||
let dayPeriod = this.mDayPeriodField.value;
|
||||
if (dayPeriod == this.mPMIndicator &&
|
||||
hour < this.mMaxHourInHour12) {
|
||||
hour += this.mMaxHourInHour12;
|
||||
} else if (dayPeriod == this.mAMIndicator &&
|
||||
hour == this.mMaxHourInHour12) {
|
||||
hour = 0;
|
||||
}
|
||||
}
|
||||
|
||||
hour = (hour < 10) ? ("0" + hour) : hour;
|
||||
|
||||
let time = hour + ":" + this.mMinuteField.value;
|
||||
if (this.mSecondField) {
|
||||
time += ":" + this.mSecondField.value;
|
||||
}
|
||||
|
||||
if (this.mMillisecField) {
|
||||
time += "." + this.mMillisecField.value;
|
||||
}
|
||||
|
||||
this.log("setInputValueFromFields: " + time);
|
||||
this.mInputElement.setUserInput(time);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="setFieldsFromPicker">
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let hour = aValue.hour;
|
||||
let minute = aValue.minute;
|
||||
this.log("setFieldsFromPicker: " + hour + ":" + minute);
|
||||
|
||||
if (!this.isEmpty(hour)) {
|
||||
this.setFieldValue(this.mHourField, hour);
|
||||
if (this.mHour12) {
|
||||
this.mDayPeriodField.value =
|
||||
(hour >= this.mMaxHourInHour12) ? this.mPMIndicator
|
||||
: this.mAMIndicator;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.isEmpty(minute)) {
|
||||
this.setFieldValue(this.mMinuteField, minute);
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="clearInputFields">
|
||||
<parameter name="aFromInputElement"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.log("clearInputFields");
|
||||
|
||||
if (this.isDisabled() || this.isReadonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mHourField && !this.mHourField.disabled &&
|
||||
!this.mHourField.readOnly) {
|
||||
this.mHourField.value = "";
|
||||
}
|
||||
|
||||
if (this.mMinuteField && !this.mMinuteField.disabled &&
|
||||
!this.mMinuteField.readOnly) {
|
||||
this.mMinuteField.value = "";
|
||||
}
|
||||
|
||||
if (this.mSecondField && !this.mSecondField.disabled &&
|
||||
!this.mSecondField.readOnly) {
|
||||
this.mSecondField.value = "";
|
||||
}
|
||||
|
||||
if (this.mMillisecField && !this.mMillisecField.disabled &&
|
||||
!this.mMillisecField.readOnly) {
|
||||
this.mMillisecField.value = "";
|
||||
}
|
||||
|
||||
if (this.mDayPeriodField && !this.mDayPeriodField.disabled &&
|
||||
!this.mDayPeriodField.readOnly) {
|
||||
this.mDayPeriodField.value = "";
|
||||
}
|
||||
|
||||
if (!aFromInputElement) {
|
||||
this.mInputElement.setUserInput("");
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="incrementFieldValue">
|
||||
<parameter name="aTargetField"/>
|
||||
<parameter name="aTimes"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let value;
|
||||
|
||||
// Use current time if field is empty.
|
||||
if (this.isEmpty(aTargetField.value)) {
|
||||
let now = new Date();
|
||||
|
||||
if (aTargetField == this.mHourField) {
|
||||
value = now.getHours() % this.mMaxHourInHour12 ||
|
||||
this.mMaxHourInHour12;
|
||||
} else if (aTargetField == this.mMinuteField) {
|
||||
value = now.getMinutes();
|
||||
} else if (aTargetField == this.mSecondField) {
|
||||
value = now.getSeconds();
|
||||
} else if (aTargetField == this.mMillisecondsField) {
|
||||
value = now.getMilliseconds();
|
||||
} else {
|
||||
this.log("Field not supported in incrementFieldValue.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
value = Number(aTargetField.value);
|
||||
}
|
||||
|
||||
let min = aTargetField.getAttribute("min");
|
||||
let max = aTargetField.getAttribute("max");
|
||||
|
||||
value += 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;
|
||||
|
||||
if (this.mDayPeriodField &&
|
||||
targetField == this.mDayPeriodField) {
|
||||
// Home/End key does nothing on AM/PM field.
|
||||
if (key == "Home" || key == "End") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mDayPeriodField.value =
|
||||
this.mDayPeriodField.value == this.mAMIndicator ?
|
||||
this.mPMIndicator : this.mAMIndicator;
|
||||
this.mDayPeriodField.select();
|
||||
this.setInputValueFromFields();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case "ArrowUp":
|
||||
this.incrementFieldValue(targetField, 1);
|
||||
break;
|
||||
case "ArrowDown":
|
||||
this.incrementFieldValue(targetField, -1);
|
||||
break;
|
||||
case "PageUp":
|
||||
this.incrementFieldValue(targetField,
|
||||
targetField == this.mHourField ? this.mHourPageUpDownInterval
|
||||
: this.mMinSecPageUpDownInterval);
|
||||
break;
|
||||
case "PageDown":
|
||||
this.incrementFieldValue(targetField,
|
||||
targetField == this.mHourField ? (0 - this.mHourPageUpDownInterval)
|
||||
: (0 - this.mMinSecPageUpDownInterval));
|
||||
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="handleKeypress">
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.isDisabled() || this.isReadonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let targetField = aEvent.originalTarget;
|
||||
let key = aEvent.key;
|
||||
|
||||
if (this.mDayPeriodField &&
|
||||
targetField == this.mDayPeriodField) {
|
||||
if (key == "a" || key == "A") {
|
||||
this.mDayPeriodField.value = this.mAMIndicator;
|
||||
this.mDayPeriodField.select();
|
||||
} else if (key == "p" || key == "P") {
|
||||
this.mDayPeriodField.value = this.mPMIndicator;
|
||||
this.mDayPeriodField.select();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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="setFieldValue">
|
||||
<parameter name="aField"/>
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let value = Number(aValue);
|
||||
if (isNaN(value)) {
|
||||
this.log("NaN on setFieldValue!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (aField.maxLength == this.mMaxLength) { // For hour, minute and second
|
||||
if (aField == this.mHourField && this.mHour12) {
|
||||
value = (value > this.mMaxHourInHour12) ?
|
||||
value - this.mMaxHourInHour12 : value;
|
||||
if (aValue == "00") {
|
||||
value = this.mMaxHourInHour12;
|
||||
}
|
||||
}
|
||||
// prepend zero
|
||||
if (value < 10) {
|
||||
value = "0" + value;
|
||||
}
|
||||
} else if (aField.maxLength == this.mMillisecMaxLength) {
|
||||
// prepend zeroes
|
||||
if (value < 10) {
|
||||
value = "00" + value;
|
||||
} else if (value < 100) {
|
||||
value = "0" + value;
|
||||
}
|
||||
}
|
||||
|
||||
aField.value = value;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="isValueAvailable">
|
||||
<body>
|
||||
<![CDATA[
|
||||
// Picker only cares about hour:minute.
|
||||
return !this.isEmpty(this.mHourField.value) ||
|
||||
!this.isEmpty(this.mMinuteField.value);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="getCurrentValue">
|
||||
<body>
|
||||
<![CDATA[
|
||||
let hour;
|
||||
if (!this.isEmpty(this.mHourField.value)) {
|
||||
hour = Number(this.mHourField.value);
|
||||
if (this.mHour12) {
|
||||
let dayPeriod = this.mDayPeriodField.value;
|
||||
if (dayPeriod == this.mPMIndicator &&
|
||||
hour < this.mMaxHourInHour12) {
|
||||
hour += this.mMaxHourInHour12;
|
||||
} else if (dayPeriod == this.mAMIndicator &&
|
||||
hour == this.mMaxHourInHour12) {
|
||||
hour = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let minute;
|
||||
if (!this.isEmpty(this.mMinuteField.value)) {
|
||||
minute = Number(this.mMinuteField.value);
|
||||
}
|
||||
|
||||
// Picker only needs hour/minute.
|
||||
let time = { hour, minute };
|
||||
|
||||
this.log("getCurrentValue: " + JSON.stringify(time));
|
||||
return time;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="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>
|
||||
|
||||
<content>
|
||||
<html:div class="datetime-input-box-wrapper"
|
||||
xbl:inherits="context,disabled,readonly">
|
||||
<html:span>
|
||||
<html:input anonid="input-one"
|
||||
class="textbox-input datetime-input numeric"
|
||||
size="2" maxlength="2"
|
||||
xbl:inherits="disabled,readonly,tabindex"/>
|
||||
<html:span anonid="sep-first" class="datetime-separator"></html:span>
|
||||
<html:input anonid="input-two"
|
||||
class="textbox-input datetime-input numeric"
|
||||
size="2" maxlength="2"
|
||||
xbl:inherits="disabled,readonly,tabindex"/>
|
||||
<html:span anonid="sep-second" class="datetime-separator"></html:span>
|
||||
<html:input anonid="input-three"
|
||||
class="textbox-input datetime-input numeric"
|
||||
size="2" maxlength="2"
|
||||
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:div>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIDateTimeInputArea">
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
this.DEBUG = false;
|
||||
this.mInputElement = this.parentNode;
|
||||
|
||||
this.mMin = this.mInputElement.min;
|
||||
this.mMax = this.mInputElement.max;
|
||||
this.mStep = this.mInputElement.step;
|
||||
this.mIsPickerOpen = false;
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<method name="log">
|
||||
<parameter name="aMsg"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.DEBUG) {
|
||||
dump("[DateTimeBox] " + aMsg + "\n");
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="focusInnerTextBox">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.log("focusInnerTextBox");
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "input-one").focus();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="blurInnerTextBox">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.log("blurInnerTextBox");
|
||||
if (this.mLastFocusedField) {
|
||||
this.mLastFocusedField.blur();
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="notifyInputElementValueChanged">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.log("inputElementValueChanged");
|
||||
this.setFieldsFromInputValue();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="setValueFromPicker">
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.setFieldsFromPicker(aValue);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="advanceToNextField">
|
||||
<parameter name="aReverse"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.log("advanceToNextField");
|
||||
|
||||
let focusedInput = this.mLastFocusedField;
|
||||
let next = aReverse ? focusedInput.previousElementSibling
|
||||
: focusedInput.nextElementSibling;
|
||||
if (!next && !aReverse) {
|
||||
this.setInputValueFromFields();
|
||||
return;
|
||||
}
|
||||
|
||||
while (next) {
|
||||
if (next.type == "text" && !next.disabled) {
|
||||
next.focus();
|
||||
break;
|
||||
}
|
||||
next = aReverse ? next.previousElementSibling
|
||||
: next.nextElementSibling;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="setPickerState">
|
||||
<parameter name="aIsOpen"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.log("picker is now " + (aIsOpen ? "opened" : "closed"));
|
||||
this.mIsPickerOpen = aIsOpen;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="isEmpty">
|
||||
<parameter name="aValue"/>
|
||||
<body>
|
||||
return (aValue == undefined || 0 === aValue.length);
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="clearInputFields">
|
||||
<body>
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="setFieldsFromInputValue">
|
||||
<body>
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="setInputValueFromFields">
|
||||
<body>
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="setFieldsFromPicker">
|
||||
<body>
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="handleKeypress">
|
||||
<body>
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="handleKeyboardNav">
|
||||
<body>
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="notifyPicker">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.mIsPickerOpen && this.isValueAvailable()) {
|
||||
this.mInputElement.updateDateTimePicker(this.getCurrentValue());
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="isDisabled">
|
||||
<body>
|
||||
<![CDATA[
|
||||
return this.hasAttribute("disabled");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="isReadonly">
|
||||
<body>
|
||||
<![CDATA[
|
||||
return this.hasAttribute("readonly");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="focus">
|
||||
<![CDATA[
|
||||
this.log("focus on: " + event.originalTarget);
|
||||
|
||||
let target = event.originalTarget;
|
||||
if (target.type == "text") {
|
||||
this.mLastFocusedField = target;
|
||||
target.select();
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="blur">
|
||||
<![CDATA[
|
||||
this.setInputValueFromFields();
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<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;
|
||||
}
|
||||
|
||||
if (!(event.originalTarget instanceof HTMLButtonElement)) {
|
||||
this.mInputElement.openDateTimePicker(this.getCurrentValue());
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="keypress" phase="capturing">
|
||||
<![CDATA[
|
||||
let key = event.key;
|
||||
this.log("keypress: " + key);
|
||||
|
||||
if (key == "Backspace" || key == "Tab") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == "Enter" || key == " ") {
|
||||
// Close picker on Enter and Space.
|
||||
this.mInputElement.closeDateTimePicker();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
165
toolkit/modules/DateTimePickerHelper.jsm
Normal file
165
toolkit/modules/DateTimePickerHelper.jsm
Normal file
@ -0,0 +1,165 @@
|
||||
/* 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 Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const DEBUG = false;
|
||||
function debug(aStr) {
|
||||
if (DEBUG) {
|
||||
dump("-*- DateTimePickerHelper: " + aStr + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"DateTimePickerHelper"
|
||||
];
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
/*
|
||||
* DateTimePickerHelper receives message from content side (input box) and
|
||||
* is reposible for opening, closing and updating the picker. Similary,
|
||||
* DateTimePickerHelper listens for picker's events and notifies the content
|
||||
* side (input box) about them.
|
||||
*/
|
||||
this.DateTimePickerHelper = {
|
||||
picker: null,
|
||||
weakBrowser: null,
|
||||
|
||||
MESSAGES: [
|
||||
"FormDateTime:OpenPicker",
|
||||
"FormDateTime:ClosePicker",
|
||||
"FormDateTime:UpdatePicker"
|
||||
],
|
||||
|
||||
init: function() {
|
||||
for (let msg of this.MESSAGES) {
|
||||
Services.mm.addMessageListener(msg, this);
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
for (let msg of this.MESSAGES) {
|
||||
Services.mm.removeMessageListener(msg, this);
|
||||
}
|
||||
},
|
||||
|
||||
// nsIMessageListener
|
||||
receiveMessage: function(aMessage) {
|
||||
debug("receiveMessage: " + aMessage.name);
|
||||
switch (aMessage.name) {
|
||||
case "FormDateTime:OpenPicker": {
|
||||
this.showPicker(aMessage.target, aMessage.data);
|
||||
break;
|
||||
}
|
||||
case "FormDateTime:ClosePicker": {
|
||||
if (!this.picker) {
|
||||
return;
|
||||
}
|
||||
this.picker.hidePopup();
|
||||
break;
|
||||
}
|
||||
case "FormDateTime:UpdatePicker": {
|
||||
let value = aMessage.data.value;
|
||||
debug("Input box value is now: " + value.hour + ":" + value.minute);
|
||||
// TODO: updating picker will be handled in Bug 1283384.
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// nsIDOMEventListener
|
||||
handleEvent: function(aEvent) {
|
||||
debug("handleEvent: " + aEvent.type);
|
||||
switch (aEvent.type) {
|
||||
case "DateTimePickerValueChanged": {
|
||||
this.updateInputBoxValue(aEvent);
|
||||
break;
|
||||
}
|
||||
case "popuphidden": {
|
||||
let browser = this.weakBrowser ? this.weakBrowser.get() : null;
|
||||
if (browser) {
|
||||
browser.messageManager.sendAsyncMessage("FormDateTime:PickerClosed");
|
||||
}
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// 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 });
|
||||
}
|
||||
},
|
||||
|
||||
// Get picker from browser and show it anchored to the input box.
|
||||
showPicker: function(aBrowser, aData) {
|
||||
let rect = aData.rect;
|
||||
let dir = aData.dir;
|
||||
let type = aData.type;
|
||||
let detail = aData.detail;
|
||||
debug("Opening picker with details: " + JSON.stringify(detail));
|
||||
|
||||
let window = aBrowser.ownerDocument.defaultView;
|
||||
let tabbrowser = window.gBrowser;
|
||||
if (Services.focus.activeWindow != window ||
|
||||
tabbrowser.selectedBrowser != aBrowser) {
|
||||
// We were sent a message from a window or tab that went into the
|
||||
// background, so we'll ignore it for now.
|
||||
return;
|
||||
}
|
||||
|
||||
this.weakBrowser = Cu.getWeakReference(aBrowser);
|
||||
this.picker = aBrowser.dateTimePicker;
|
||||
if (!this.picker) {
|
||||
debug("aBrowser.dateTimePicker not found, exiting now.");
|
||||
return;
|
||||
}
|
||||
this.picker.hidden = false;
|
||||
this.picker.openPopupAtScreenRect("after_start", rect.left, rect.top,
|
||||
rect.width, rect.height, false, false);
|
||||
this.addPickerListeners();
|
||||
},
|
||||
|
||||
// Picker is closed, do some cleanup.
|
||||
close: function() {
|
||||
this.removePickerListeners();
|
||||
this.picker = null;
|
||||
this.weakBrowser = null;
|
||||
},
|
||||
|
||||
// Listen to picker's event.
|
||||
addPickerListeners: function() {
|
||||
if (!this.picker) {
|
||||
return;
|
||||
}
|
||||
this.picker.addEventListener("popuphidden", this);
|
||||
this.picker.addEventListener("DateTimePickerValueChanged", this);
|
||||
},
|
||||
|
||||
// Stop listening to picker's event.
|
||||
removePickerListeners: function() {
|
||||
if (!this.picker) {
|
||||
return;
|
||||
}
|
||||
this.picker.removeEventListener("popuphidden", this);
|
||||
this.picker.removeEventListener("DateTimePickerValueChanged", this);
|
||||
},
|
||||
};
|
@ -35,6 +35,7 @@ EXTRA_JS_MODULES += [
|
||||
'ClientID.jsm',
|
||||
'Color.jsm',
|
||||
'Console.jsm',
|
||||
'DateTimePickerHelper.jsm',
|
||||
'debug.js',
|
||||
'DeferredTask.jsm',
|
||||
'Deprecated.jsm',
|
||||
|
7
toolkit/themes/shared/icons/input-clear.svg
Normal file
7
toolkit/themes/shared/icons/input-clear.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
|
||||
<path id="Combined-Shape" d="M6,12c3.3,0,6-2.7,6-6S9.3,0,6,0S0,2.7,0,6S2.7,12,6,12z M9,8.1L8.1,9L6,6.9L3.9,9L3,8.1L5.1,6L3,3.9
|
||||
L3.9,3L6,5.1L8.1,3L9,3.9L6.9,6L9,8.1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 521 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user