Merge m-c to autoland. a=merge

This commit is contained in:
Ryan VanderMeulen 2016-10-07 09:45:32 -04:00
commit 35753f09fc
102 changed files with 3217 additions and 309 deletions

View File

@ -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

View File

@ -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>

View File

@ -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) {

View File

@ -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();
}

View File

@ -29,3 +29,4 @@ support-files =
[browser_blobURLIsolation.js]
[browser_imageCacheIsolation.js]
[browser_sharedworker.js]
[browser_httpauth.js]

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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'
};

View File

@ -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() {

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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": {}
}
};
}
}
});

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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);
};

View File

@ -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");
});

View File

@ -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();

View File

@ -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")

View 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>

View 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>

View 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>

View File

@ -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

View File

@ -649,6 +649,8 @@ HTMLImageElement::UnbindFromTree(bool aDeep, bool aNullParent)
}
}
mLastSelectedSource = nullptr;
nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
}

View File

@ -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;

View File

@ -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 {

View File

@ -18,6 +18,7 @@ MOCHITEST_CHROME_MANIFESTS += [
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
XPIDL_SOURCES += [
'nsIDateTimeInputArea.idl',
'nsIFormSubmitObserver.idl',
'nsIHTMLMenu.idl',
'nsIImageDocument.idl',

View 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);
};

View File

@ -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);

View 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>

View 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>

View File

@ -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

View File

@ -0,0 +1 @@
<div><img src=pass.png></div>

View 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>

View File

@ -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.

View File

@ -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'

View File

@ -0,0 +1,58 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
-->
<head>
<title>Test focus/blur behaviour for &lt;input type='time'&gt;</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>

View File

@ -0,0 +1,72 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
-->
<head>
<title>Test tabindex attribute for &lt;input type='time'&gt;</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>

View 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>

View File

@ -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: [

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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;
};

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
};
}
}

View File

@ -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

View File

@ -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() {}
};

View File

@ -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

View File

@ -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();

View File

@ -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"));

View File

@ -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();
};

View File

@ -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");
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -22,6 +22,7 @@ UNIFIED_SOURCES += [
'nsButtonFrameRenderer.cpp',
'nsColorControlFrame.cpp',
'nsComboboxControlFrame.cpp',
'nsDateTimeControlFrame.cpp',
'nsFieldSetFrame.cpp',
'nsFileControlFrame.cpp',
'nsFormControlFrame.cpp',

View 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;
}

View 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__

View File

@ -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]

View 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>

View File

@ -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)

View File

@ -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()) {

View File

@ -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);

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<input type="checkbox" style="-moz-appearance:none;">
</body>
</html>

View File

@ -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>

View 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

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<input type="time" value="12:30">
</body>
</html>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<input type="time" value="12:30" style="border:10px solid blue">
</body>
</html>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<input type="time" value="12:30" style="font-size: 32px;">
</body>
</html>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<input type="text" style="-moz-appearance:none;">
</body>
</html>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<input type="time" style="-moz-appearance:none;">
</body>
</html>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<input type="time" style="width:200px; height:50px">
</body>
</html>

View File

@ -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>

View File

@ -11,3 +11,4 @@ include text/reftest.list
include percentage/reftest.list
include hidden/reftest.list
include color/reftest.list
include datetime/reftest.list

View File

@ -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") {

View File

@ -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

View File

@ -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);

View File

@ -1,5 +0,0 @@
[encrypted-media-keystatuses.html]
type: testharness
[Verify MediaKeySession.keyStatuses.]
expected: FAIL

View File

@ -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();

View File

@ -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();

View File

@ -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)

View File

@ -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[

View 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;
}

View 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>

View 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);
},
};

View File

@ -35,6 +35,7 @@ EXTRA_JS_MODULES += [
'ClientID.jsm',
'Color.jsm',
'Console.jsm',
'DateTimePickerHelper.jsm',
'debug.js',
'DeferredTask.jsm',
'Deprecated.jsm',

View 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