|
1 /* |
|
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
|
3 * (C) 1999 Antti Koivisto (koivisto@kde.org) |
|
4 * (C) 2001 Dirk Mueller (mueller@kde.org) |
|
5 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. |
|
6 * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
|
7 * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) |
|
8 * Copyright (C) 2010 Google Inc. All rights reserved. |
|
9 * |
|
10 * This library is free software; you can redistribute it and/or |
|
11 * modify it under the terms of the GNU Library General Public |
|
12 * License as published by the Free Software Foundation; either |
|
13 * version 2 of the License, or (at your option) any later version. |
|
14 * |
|
15 * This library is distributed in the hope that it will be useful, |
|
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
18 * Library General Public License for more details. |
|
19 * |
|
20 * You should have received a copy of the GNU Library General Public License |
|
21 * along with this library; see the file COPYING.LIB. If not, write to |
|
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
23 * Boston, MA 02110-1301, USA. |
|
24 * |
|
25 */ |
|
26 |
|
27 #include "config.h" |
|
28 #include "HTMLInputElement.h" |
|
29 |
|
30 #include "AXObjectCache.h" |
|
31 #include "Attribute.h" |
|
32 #include "BeforeTextInsertedEvent.h" |
|
33 #include "CSSPropertyNames.h" |
|
34 #include "ChromeClient.h" |
|
35 #include "DateComponents.h" |
|
36 #include "Document.h" |
|
37 #include "Editor.h" |
|
38 #include "Event.h" |
|
39 #include "EventHandler.h" |
|
40 #include "EventNames.h" |
|
41 #include "ExceptionCode.h" |
|
42 #include "File.h" |
|
43 #include "FileList.h" |
|
44 #include "FileSystem.h" |
|
45 #include "FocusController.h" |
|
46 #include "FormDataList.h" |
|
47 #include "Frame.h" |
|
48 #include "HTMLDataListElement.h" |
|
49 #include "HTMLFormElement.h" |
|
50 #include "HTMLImageLoader.h" |
|
51 #include "HTMLNames.h" |
|
52 #include "HTMLOptionElement.h" |
|
53 #include "LegacyHTMLTreeBuilder.h" |
|
54 #include "KeyboardEvent.h" |
|
55 #include "LocalizedStrings.h" |
|
56 #include "MouseEvent.h" |
|
57 #include "Page.h" |
|
58 #include "RegularExpression.h" |
|
59 #include "RenderButton.h" |
|
60 #include "RenderFileUploadControl.h" |
|
61 #include "RenderImage.h" |
|
62 #include "RenderSlider.h" |
|
63 #include "RenderText.h" |
|
64 #include "RenderTextControlSingleLine.h" |
|
65 #include "RenderTheme.h" |
|
66 #include "ScriptEventListener.h" |
|
67 #include "StepRange.h" |
|
68 #include "StringHash.h" |
|
69 #include "TextEvent.h" |
|
70 #include <wtf/HashMap.h> |
|
71 #include <wtf/MathExtras.h> |
|
72 #include <wtf/StdLibExtras.h> |
|
73 |
|
74 using namespace std; |
|
75 |
|
76 namespace WebCore { |
|
77 |
|
78 using namespace HTMLNames; |
|
79 |
|
80 const int maxSavedResults = 256; |
|
81 |
|
82 // Constant values for getAllowedValueStep(). |
|
83 static const double dateDefaultStep = 1.0; |
|
84 static const double dateStepScaleFactor = 86400000.0; |
|
85 static const double dateTimeDefaultStep = 60.0; |
|
86 static const double dateTimeStepScaleFactor = 1000.0; |
|
87 static const double monthDefaultStep = 1.0; |
|
88 static const double monthStepScaleFactor = 1.0; |
|
89 static const double numberDefaultStep = 1.0; |
|
90 static const double numberStepScaleFactor = 1.0; |
|
91 static const double timeDefaultStep = 60.0; |
|
92 static const double timeStepScaleFactor = 1000.0; |
|
93 static const double weekDefaultStep = 1.0; |
|
94 static const double weekStepScaleFactor = 604800000.0; |
|
95 |
|
96 // Constant values for minimum(). |
|
97 static const double numberDefaultMinimum = -DBL_MAX; |
|
98 static const double rangeDefaultMinimum = 0.0; |
|
99 |
|
100 // Constant values for maximum(). |
|
101 static const double numberDefaultMaximum = DBL_MAX; |
|
102 static const double rangeDefaultMaximum = 100.0; |
|
103 |
|
104 static const double defaultStepBase = 0.0; |
|
105 static const double weekDefaultStepBase = -259200000.0; // The first day of 1970-W01. |
|
106 |
|
107 static const double msecPerMinute = 60 * 1000; |
|
108 static const double msecPerSecond = 1000; |
|
109 |
|
110 static bool isNumberCharacter(UChar ch) |
|
111 { |
|
112 return ch == '+' || ch == '-' || ch == '.' || ch == 'e' || ch == 'E' |
|
113 || ch >= '0' && ch <= '9'; |
|
114 } |
|
115 |
|
116 HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form) |
|
117 : HTMLTextFormControlElement(tagName, document, form) |
|
118 , m_xPos(0) |
|
119 , m_yPos(0) |
|
120 , m_maxResults(-1) |
|
121 , m_type(TEXT) |
|
122 , m_checked(false) |
|
123 , m_defaultChecked(false) |
|
124 , m_useDefaultChecked(true) |
|
125 , m_indeterminate(false) |
|
126 , m_haveType(false) |
|
127 , m_activeSubmit(false) |
|
128 , m_autocomplete(Uninitialized) |
|
129 , m_autofilled(false) |
|
130 , m_inited(false) |
|
131 { |
|
132 ASSERT(hasTagName(inputTag) || hasTagName(isindexTag)); |
|
133 } |
|
134 |
|
135 PassRefPtr<HTMLInputElement> HTMLInputElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form) |
|
136 { |
|
137 return adoptRef(new HTMLInputElement(tagName, document, form)); |
|
138 } |
|
139 |
|
140 HTMLInputElement::~HTMLInputElement() |
|
141 { |
|
142 if (needsActivationCallback()) |
|
143 document()->unregisterForDocumentActivationCallbacks(this); |
|
144 |
|
145 document()->checkedRadioButtons().removeButton(this); |
|
146 |
|
147 // Need to remove this from the form while it is still an HTMLInputElement, |
|
148 // so can't wait for the base class's destructor to do it. |
|
149 removeFromForm(); |
|
150 } |
|
151 |
|
152 const AtomicString& HTMLInputElement::formControlName() const |
|
153 { |
|
154 return m_data.name(); |
|
155 } |
|
156 |
|
157 bool HTMLInputElement::autoComplete() const |
|
158 { |
|
159 if (m_autocomplete != Uninitialized) |
|
160 return m_autocomplete == On; |
|
161 return HTMLTextFormControlElement::autoComplete(); |
|
162 } |
|
163 |
|
164 static inline CheckedRadioButtons& checkedRadioButtons(const HTMLInputElement* element) |
|
165 { |
|
166 if (HTMLFormElement* form = element->form()) |
|
167 return form->checkedRadioButtons(); |
|
168 return element->document()->checkedRadioButtons(); |
|
169 } |
|
170 |
|
171 void HTMLInputElement::updateCheckedRadioButtons() |
|
172 { |
|
173 if (attached() && checked()) |
|
174 checkedRadioButtons(this).addButton(this); |
|
175 |
|
176 if (form()) { |
|
177 const Vector<HTMLFormControlElement*>& controls = form()->associatedElements(); |
|
178 for (unsigned i = 0; i < controls.size(); ++i) { |
|
179 HTMLFormControlElement* control = controls[i]; |
|
180 if (control->name() != name()) |
|
181 continue; |
|
182 if (control->type() != type()) |
|
183 continue; |
|
184 control->setNeedsValidityCheck(); |
|
185 } |
|
186 } else { |
|
187 // FIXME: Traversing the document is inefficient. |
|
188 for (Node* node = document()->body(); node; node = node->traverseNextNode()) { |
|
189 if (!node->isElementNode()) |
|
190 continue; |
|
191 Element* element = static_cast<Element*>(node); |
|
192 if (element->formControlName() != name()) |
|
193 continue; |
|
194 if (element->formControlType() != type()) |
|
195 continue; |
|
196 HTMLFormControlElement* control = static_cast<HTMLFormControlElement*>(element); |
|
197 if (control->form()) |
|
198 continue; |
|
199 control->setNeedsValidityCheck(); |
|
200 } |
|
201 } |
|
202 |
|
203 if (renderer() && renderer()->style()->hasAppearance()) |
|
204 renderer()->theme()->stateChanged(renderer(), CheckedState); |
|
205 } |
|
206 |
|
207 bool HTMLInputElement::valueMissing() const |
|
208 { |
|
209 if (!isRequiredFormControl() || readOnly() || disabled()) |
|
210 return false; |
|
211 |
|
212 switch (inputType()) { |
|
213 case DATE: |
|
214 case DATETIME: |
|
215 case DATETIMELOCAL: |
|
216 case EMAIL: |
|
217 case FILE: |
|
218 case MONTH: |
|
219 case NUMBER: |
|
220 case PASSWORD: |
|
221 case SEARCH: |
|
222 case TELEPHONE: |
|
223 case TEXT: |
|
224 case TIME: |
|
225 case URL: |
|
226 case WEEK: |
|
227 return value().isEmpty(); |
|
228 case CHECKBOX: |
|
229 return !checked(); |
|
230 case RADIO: |
|
231 return !checkedRadioButtons(this).checkedButtonForGroup(name()); |
|
232 case COLOR: |
|
233 return false; |
|
234 case BUTTON: |
|
235 case HIDDEN: |
|
236 case IMAGE: |
|
237 case ISINDEX: |
|
238 case RANGE: |
|
239 case RESET: |
|
240 case SUBMIT: |
|
241 break; |
|
242 } |
|
243 |
|
244 ASSERT_NOT_REACHED(); |
|
245 return false; |
|
246 } |
|
247 |
|
248 bool HTMLInputElement::patternMismatch() const |
|
249 { |
|
250 switch (inputType()) { |
|
251 case BUTTON: |
|
252 case CHECKBOX: |
|
253 case COLOR: |
|
254 case DATE: |
|
255 case DATETIME: |
|
256 case DATETIMELOCAL: |
|
257 case FILE: |
|
258 case HIDDEN: |
|
259 case IMAGE: |
|
260 case ISINDEX: |
|
261 case MONTH: |
|
262 case NUMBER: |
|
263 case RADIO: |
|
264 case RANGE: |
|
265 case RESET: |
|
266 case SUBMIT: |
|
267 case TIME: |
|
268 case WEEK: |
|
269 return false; |
|
270 case EMAIL: |
|
271 case PASSWORD: |
|
272 case SEARCH: |
|
273 case TELEPHONE: |
|
274 case TEXT: |
|
275 case URL: |
|
276 const AtomicString& pattern = getAttribute(patternAttr); |
|
277 String value = this->value(); |
|
278 // Empty values can't be mismatched |
|
279 if (pattern.isEmpty() || value.isEmpty()) |
|
280 return false; |
|
281 RegularExpression patternRegExp(pattern, TextCaseSensitive); |
|
282 int matchLength = 0; |
|
283 int valueLength = value.length(); |
|
284 int matchOffset = patternRegExp.match(value, 0, &matchLength); |
|
285 return matchOffset || matchLength != valueLength; |
|
286 } |
|
287 ASSERT_NOT_REACHED(); |
|
288 return false; |
|
289 } |
|
290 |
|
291 bool HTMLInputElement::tooLong() const |
|
292 { |
|
293 switch (inputType()) { |
|
294 case EMAIL: |
|
295 case PASSWORD: |
|
296 case SEARCH: |
|
297 case TELEPHONE: |
|
298 case TEXT: |
|
299 case URL: { |
|
300 int max = maxLength(); |
|
301 if (max < 0) |
|
302 return false; |
|
303 // Return false for the default value even if it is longer than maxLength. |
|
304 bool userEdited = !m_data.value().isNull(); |
|
305 if (!userEdited) |
|
306 return false; |
|
307 return numGraphemeClusters(value()) > static_cast<unsigned>(max); |
|
308 } |
|
309 case BUTTON: |
|
310 case CHECKBOX: |
|
311 case COLOR: |
|
312 case DATE: |
|
313 case DATETIME: |
|
314 case DATETIMELOCAL: |
|
315 case FILE: |
|
316 case HIDDEN: |
|
317 case IMAGE: |
|
318 case ISINDEX: |
|
319 case MONTH: |
|
320 case NUMBER: |
|
321 case RADIO: |
|
322 case RANGE: |
|
323 case RESET: |
|
324 case SUBMIT: |
|
325 case TIME: |
|
326 case WEEK: |
|
327 return false; |
|
328 } |
|
329 ASSERT_NOT_REACHED(); |
|
330 return false; |
|
331 } |
|
332 |
|
333 bool HTMLInputElement::rangeUnderflow() const |
|
334 { |
|
335 const double nan = numeric_limits<double>::quiet_NaN(); |
|
336 switch (inputType()) { |
|
337 case DATE: |
|
338 case DATETIME: |
|
339 case DATETIMELOCAL: |
|
340 case MONTH: |
|
341 case NUMBER: |
|
342 case TIME: |
|
343 case WEEK: { |
|
344 double doubleValue = parseToDouble(value(), nan); |
|
345 return isfinite(doubleValue) && doubleValue < minimum(); |
|
346 } |
|
347 case RANGE: // Guaranteed by sanitization. |
|
348 ASSERT(parseToDouble(value(), nan) >= minimum()); |
|
349 case BUTTON: |
|
350 case CHECKBOX: |
|
351 case COLOR: |
|
352 case EMAIL: |
|
353 case FILE: |
|
354 case HIDDEN: |
|
355 case IMAGE: |
|
356 case ISINDEX: |
|
357 case PASSWORD: |
|
358 case RADIO: |
|
359 case RESET: |
|
360 case SEARCH: |
|
361 case SUBMIT: |
|
362 case TELEPHONE: |
|
363 case TEXT: |
|
364 case URL: |
|
365 break; |
|
366 } |
|
367 return false; |
|
368 } |
|
369 |
|
370 bool HTMLInputElement::rangeOverflow() const |
|
371 { |
|
372 const double nan = numeric_limits<double>::quiet_NaN(); |
|
373 switch (inputType()) { |
|
374 case DATE: |
|
375 case DATETIME: |
|
376 case DATETIMELOCAL: |
|
377 case MONTH: |
|
378 case NUMBER: |
|
379 case TIME: |
|
380 case WEEK: { |
|
381 double doubleValue = parseToDouble(value(), nan); |
|
382 return isfinite(doubleValue) && doubleValue > maximum(); |
|
383 } |
|
384 case RANGE: // Guaranteed by sanitization. |
|
385 ASSERT(parseToDouble(value(), nan) <= maximum()); |
|
386 case BUTTON: |
|
387 case CHECKBOX: |
|
388 case COLOR: |
|
389 case EMAIL: |
|
390 case FILE: |
|
391 case HIDDEN: |
|
392 case IMAGE: |
|
393 case ISINDEX: |
|
394 case PASSWORD: |
|
395 case RADIO: |
|
396 case RESET: |
|
397 case SEARCH: |
|
398 case SUBMIT: |
|
399 case TELEPHONE: |
|
400 case TEXT: |
|
401 case URL: |
|
402 break; |
|
403 } |
|
404 return false; |
|
405 } |
|
406 |
|
407 double HTMLInputElement::minimum() const |
|
408 { |
|
409 switch (inputType()) { |
|
410 case DATE: |
|
411 return parseToDouble(getAttribute(minAttr), DateComponents::minimumDate()); |
|
412 case DATETIME: |
|
413 case DATETIMELOCAL: |
|
414 return parseToDouble(getAttribute(minAttr), DateComponents::minimumDateTime()); |
|
415 case MONTH: |
|
416 return parseToDouble(getAttribute(minAttr), DateComponents::minimumMonth()); |
|
417 case NUMBER: |
|
418 return parseToDouble(getAttribute(minAttr), numberDefaultMinimum); |
|
419 case RANGE: |
|
420 return parseToDouble(getAttribute(minAttr), rangeDefaultMinimum); |
|
421 case TIME: |
|
422 return parseToDouble(getAttribute(minAttr), DateComponents::minimumTime()); |
|
423 case WEEK: |
|
424 return parseToDouble(getAttribute(minAttr), DateComponents::minimumWeek()); |
|
425 case BUTTON: |
|
426 case CHECKBOX: |
|
427 case COLOR: |
|
428 case EMAIL: |
|
429 case FILE: |
|
430 case HIDDEN: |
|
431 case IMAGE: |
|
432 case ISINDEX: |
|
433 case PASSWORD: |
|
434 case RADIO: |
|
435 case RESET: |
|
436 case SEARCH: |
|
437 case SUBMIT: |
|
438 case TELEPHONE: |
|
439 case TEXT: |
|
440 case URL: |
|
441 break; |
|
442 } |
|
443 ASSERT_NOT_REACHED(); |
|
444 return 0; |
|
445 } |
|
446 |
|
447 double HTMLInputElement::maximum() const |
|
448 { |
|
449 switch (inputType()) { |
|
450 case DATE: |
|
451 return parseToDouble(getAttribute(maxAttr), DateComponents::maximumDate()); |
|
452 case DATETIME: |
|
453 case DATETIMELOCAL: |
|
454 return parseToDouble(getAttribute(maxAttr), DateComponents::maximumDateTime()); |
|
455 case MONTH: |
|
456 return parseToDouble(getAttribute(maxAttr), DateComponents::maximumMonth()); |
|
457 case NUMBER: |
|
458 return parseToDouble(getAttribute(maxAttr), numberDefaultMaximum); |
|
459 case RANGE: { |
|
460 double max = parseToDouble(getAttribute(maxAttr), rangeDefaultMaximum); |
|
461 // A remedy for the inconsistent min/max values for RANGE. |
|
462 // Sets the maximum to the default or the minimum value. |
|
463 double min = minimum(); |
|
464 if (max < min) |
|
465 max = std::max(min, rangeDefaultMaximum); |
|
466 return max; |
|
467 } |
|
468 case TIME: |
|
469 return parseToDouble(getAttribute(maxAttr), DateComponents::maximumTime()); |
|
470 case WEEK: |
|
471 return parseToDouble(getAttribute(maxAttr), DateComponents::maximumWeek()); |
|
472 case BUTTON: |
|
473 case CHECKBOX: |
|
474 case COLOR: |
|
475 case EMAIL: |
|
476 case FILE: |
|
477 case HIDDEN: |
|
478 case IMAGE: |
|
479 case ISINDEX: |
|
480 case PASSWORD: |
|
481 case RADIO: |
|
482 case RESET: |
|
483 case SEARCH: |
|
484 case SUBMIT: |
|
485 case TELEPHONE: |
|
486 case TEXT: |
|
487 case URL: |
|
488 break; |
|
489 } |
|
490 ASSERT_NOT_REACHED(); |
|
491 return 0; |
|
492 } |
|
493 |
|
494 double HTMLInputElement::stepBase() const |
|
495 { |
|
496 switch (inputType()) { |
|
497 case RANGE: |
|
498 return minimum(); |
|
499 case DATE: |
|
500 case DATETIME: |
|
501 case DATETIMELOCAL: |
|
502 case MONTH: |
|
503 case NUMBER: |
|
504 case TIME: |
|
505 return parseToDouble(getAttribute(minAttr), defaultStepBase); |
|
506 case WEEK: |
|
507 return parseToDouble(getAttribute(minAttr), weekDefaultStepBase); |
|
508 case BUTTON: |
|
509 case CHECKBOX: |
|
510 case COLOR: |
|
511 case EMAIL: |
|
512 case FILE: |
|
513 case HIDDEN: |
|
514 case IMAGE: |
|
515 case ISINDEX: |
|
516 case PASSWORD: |
|
517 case RADIO: |
|
518 case RESET: |
|
519 case SEARCH: |
|
520 case SUBMIT: |
|
521 case TELEPHONE: |
|
522 case TEXT: |
|
523 case URL: |
|
524 break; |
|
525 } |
|
526 ASSERT_NOT_REACHED(); |
|
527 return 0.0; |
|
528 } |
|
529 |
|
530 bool HTMLInputElement::stepMismatch() const |
|
531 { |
|
532 double step; |
|
533 if (!getAllowedValueStep(&step)) |
|
534 return false; |
|
535 switch (inputType()) { |
|
536 case RANGE: |
|
537 // stepMismatch doesn't occur for RANGE. RenderSlider guarantees the |
|
538 // value matches to step on user input, and sanitation takes care |
|
539 // of the general case. |
|
540 return false; |
|
541 case NUMBER: { |
|
542 double doubleValue; |
|
543 if (!parseToDoubleForNumberType(value(), &doubleValue)) |
|
544 return false; |
|
545 doubleValue = fabs(doubleValue - stepBase()); |
|
546 if (isinf(doubleValue)) |
|
547 return false; |
|
548 // double's fractional part size is DBL_MAN_DIG-bit. If the current |
|
549 // value is greater than step*2^DBL_MANT_DIG, the following fmod() makes |
|
550 // no sense. |
|
551 if (doubleValue / pow(2.0, DBL_MANT_DIG) > step) |
|
552 return false; |
|
553 double remainder = fmod(doubleValue, step); |
|
554 // Accepts errors in lower 7-bit. |
|
555 double acceptableError = step / pow(2.0, DBL_MANT_DIG - 7); |
|
556 return acceptableError < remainder && remainder < (step - acceptableError); |
|
557 } |
|
558 case DATE: |
|
559 case DATETIME: |
|
560 case DATETIMELOCAL: |
|
561 case MONTH: |
|
562 case TIME: |
|
563 case WEEK: { |
|
564 const double nan = numeric_limits<double>::quiet_NaN(); |
|
565 double doubleValue = parseToDouble(value(), nan); |
|
566 doubleValue = fabs(doubleValue - stepBase()); |
|
567 if (!isfinite(doubleValue)) |
|
568 return false; |
|
569 ASSERT(round(doubleValue) == doubleValue); |
|
570 ASSERT(round(step) == step); |
|
571 return fmod(doubleValue, step); |
|
572 } |
|
573 case BUTTON: |
|
574 case CHECKBOX: |
|
575 case COLOR: |
|
576 case EMAIL: |
|
577 case FILE: |
|
578 case HIDDEN: |
|
579 case IMAGE: |
|
580 case ISINDEX: |
|
581 case PASSWORD: |
|
582 case RADIO: |
|
583 case RESET: |
|
584 case SEARCH: |
|
585 case SUBMIT: |
|
586 case TELEPHONE: |
|
587 case TEXT: |
|
588 case URL: |
|
589 break; |
|
590 } |
|
591 // Non-supported types should be rejected by getAllowedValueStep(). |
|
592 ASSERT_NOT_REACHED(); |
|
593 return false; |
|
594 } |
|
595 |
|
596 bool HTMLInputElement::getStepParameters(double* defaultStep, double* stepScaleFactor) const |
|
597 { |
|
598 ASSERT(defaultStep); |
|
599 ASSERT(stepScaleFactor); |
|
600 switch (inputType()) { |
|
601 case NUMBER: |
|
602 case RANGE: |
|
603 *defaultStep = numberDefaultStep; |
|
604 *stepScaleFactor = numberStepScaleFactor; |
|
605 return true; |
|
606 case DATE: |
|
607 *defaultStep = dateDefaultStep; |
|
608 *stepScaleFactor = dateStepScaleFactor; |
|
609 return true; |
|
610 case DATETIME: |
|
611 case DATETIMELOCAL: |
|
612 *defaultStep = dateTimeDefaultStep; |
|
613 *stepScaleFactor = dateTimeStepScaleFactor; |
|
614 return true; |
|
615 case MONTH: |
|
616 *defaultStep = monthDefaultStep; |
|
617 *stepScaleFactor = monthStepScaleFactor; |
|
618 return true; |
|
619 case TIME: |
|
620 *defaultStep = timeDefaultStep; |
|
621 *stepScaleFactor = timeStepScaleFactor; |
|
622 return true; |
|
623 case WEEK: |
|
624 *defaultStep = weekDefaultStep; |
|
625 *stepScaleFactor = weekStepScaleFactor; |
|
626 return true; |
|
627 case BUTTON: |
|
628 case CHECKBOX: |
|
629 case COLOR: |
|
630 case EMAIL: |
|
631 case FILE: |
|
632 case HIDDEN: |
|
633 case IMAGE: |
|
634 case ISINDEX: |
|
635 case PASSWORD: |
|
636 case RADIO: |
|
637 case RESET: |
|
638 case SEARCH: |
|
639 case SUBMIT: |
|
640 case TELEPHONE: |
|
641 case TEXT: |
|
642 case URL: |
|
643 return false; |
|
644 } |
|
645 ASSERT_NOT_REACHED(); |
|
646 return false; |
|
647 } |
|
648 |
|
649 bool HTMLInputElement::getAllowedValueStep(double* step) const |
|
650 { |
|
651 ASSERT(step); |
|
652 double defaultStep; |
|
653 double stepScaleFactor; |
|
654 if (!getStepParameters(&defaultStep, &stepScaleFactor)) |
|
655 return false; |
|
656 const AtomicString& stepString = getAttribute(stepAttr); |
|
657 if (stepString.isEmpty()) { |
|
658 *step = defaultStep * stepScaleFactor; |
|
659 return true; |
|
660 } |
|
661 if (equalIgnoringCase(stepString, "any")) |
|
662 return false; |
|
663 double parsed; |
|
664 if (!parseToDoubleForNumberType(stepString, &parsed) || parsed <= 0.0) { |
|
665 *step = defaultStep * stepScaleFactor; |
|
666 return true; |
|
667 } |
|
668 // For DATE, MONTH, WEEK, the parsed value should be an integer. |
|
669 if (inputType() == DATE || inputType() == MONTH || inputType() == WEEK) |
|
670 parsed = max(round(parsed), 1.0); |
|
671 double result = parsed * stepScaleFactor; |
|
672 // For DATETIME, DATETIMELOCAL, TIME, the result should be an integer. |
|
673 if (inputType() == DATETIME || inputType() == DATETIMELOCAL || inputType() == TIME) |
|
674 result = max(round(result), 1.0); |
|
675 ASSERT(result > 0); |
|
676 *step = result; |
|
677 return true; |
|
678 } |
|
679 |
|
680 void HTMLInputElement::applyStep(double count, ExceptionCode& ec) |
|
681 { |
|
682 double step; |
|
683 if (!getAllowedValueStep(&step)) { |
|
684 ec = INVALID_STATE_ERR; |
|
685 return; |
|
686 } |
|
687 const double nan = numeric_limits<double>::quiet_NaN(); |
|
688 double current = parseToDouble(value(), nan); |
|
689 if (!isfinite(current)) { |
|
690 ec = INVALID_STATE_ERR; |
|
691 return; |
|
692 } |
|
693 double newValue = current + step * count; |
|
694 if (isinf(newValue)) { |
|
695 ec = INVALID_STATE_ERR; |
|
696 return; |
|
697 } |
|
698 if (newValue < minimum()) { |
|
699 ec = INVALID_STATE_ERR; |
|
700 return; |
|
701 } |
|
702 double base = stepBase(); |
|
703 newValue = base + round((newValue - base) / step) * step; |
|
704 if (newValue > maximum()) { |
|
705 ec = INVALID_STATE_ERR; |
|
706 return; |
|
707 } |
|
708 setValueAsNumber(newValue, ec); |
|
709 } |
|
710 |
|
711 void HTMLInputElement::stepUp(int n, ExceptionCode& ec) |
|
712 { |
|
713 applyStep(n, ec); |
|
714 } |
|
715 |
|
716 void HTMLInputElement::stepDown(int n, ExceptionCode& ec) |
|
717 { |
|
718 applyStep(-n, ec); |
|
719 } |
|
720 |
|
721 bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const |
|
722 { |
|
723 // If text fields can be focused, then they should always be keyboard focusable |
|
724 if (isTextField()) |
|
725 return HTMLFormControlElementWithState::isFocusable(); |
|
726 |
|
727 // If the base class says we can't be focused, then we can stop now. |
|
728 if (!HTMLFormControlElementWithState::isKeyboardFocusable(event)) |
|
729 return false; |
|
730 |
|
731 if (inputType() == RADIO) { |
|
732 |
|
733 // Never allow keyboard tabbing to leave you in the same radio group. Always |
|
734 // skip any other elements in the group. |
|
735 Node* currentFocusedNode = document()->focusedNode(); |
|
736 if (currentFocusedNode && currentFocusedNode->hasTagName(inputTag)) { |
|
737 HTMLInputElement* focusedInput = static_cast<HTMLInputElement*>(currentFocusedNode); |
|
738 if (focusedInput->inputType() == RADIO && focusedInput->form() == form() && focusedInput->name() == name()) |
|
739 return false; |
|
740 } |
|
741 |
|
742 // Allow keyboard focus if we're checked or if nothing in the group is checked. |
|
743 return checked() || !checkedRadioButtons(this).checkedButtonForGroup(name()); |
|
744 } |
|
745 |
|
746 return true; |
|
747 } |
|
748 |
|
749 bool HTMLInputElement::isMouseFocusable() const |
|
750 { |
|
751 if (isTextField()) |
|
752 return HTMLFormControlElementWithState::isFocusable(); |
|
753 return HTMLFormControlElementWithState::isMouseFocusable(); |
|
754 } |
|
755 |
|
756 void HTMLInputElement::updateFocusAppearance(bool restorePreviousSelection) |
|
757 { |
|
758 if (isTextField()) |
|
759 InputElement::updateFocusAppearance(m_data, this, this, restorePreviousSelection); |
|
760 else |
|
761 HTMLFormControlElementWithState::updateFocusAppearance(restorePreviousSelection); |
|
762 } |
|
763 |
|
764 void HTMLInputElement::aboutToUnload() |
|
765 { |
|
766 InputElement::aboutToUnload(this, this); |
|
767 } |
|
768 |
|
769 bool HTMLInputElement::shouldUseInputMethod() const |
|
770 { |
|
771 // The reason IME's are disabled for the password field is because IMEs |
|
772 // can access the underlying password and display it in clear text -- |
|
773 // e.g. you can use it to access the stored password for any site |
|
774 // with only trivial effort. |
|
775 return isTextField() && inputType() != PASSWORD; |
|
776 } |
|
777 |
|
778 void HTMLInputElement::handleFocusEvent() |
|
779 { |
|
780 InputElement::dispatchFocusEvent(this, this); |
|
781 } |
|
782 |
|
783 void HTMLInputElement::handleBlurEvent() |
|
784 { |
|
785 InputElement::dispatchBlurEvent(this, this); |
|
786 } |
|
787 |
|
788 void HTMLInputElement::setType(const String& t) |
|
789 { |
|
790 if (t.isEmpty()) { |
|
791 int exccode; |
|
792 removeAttribute(typeAttr, exccode); |
|
793 } else |
|
794 setAttribute(typeAttr, t); |
|
795 } |
|
796 |
|
797 typedef HashMap<String, HTMLInputElement::InputType, CaseFoldingHash> InputTypeMap; |
|
798 static const InputTypeMap* createTypeMap() |
|
799 { |
|
800 InputTypeMap* map = new InputTypeMap; |
|
801 map->add("button", HTMLInputElement::BUTTON); |
|
802 map->add("checkbox", HTMLInputElement::CHECKBOX); |
|
803 map->add("color", HTMLInputElement::COLOR); |
|
804 map->add("date", HTMLInputElement::DATE); |
|
805 map->add("datetime", HTMLInputElement::DATETIME); |
|
806 map->add("datetime-local", HTMLInputElement::DATETIMELOCAL); |
|
807 map->add("email", HTMLInputElement::EMAIL); |
|
808 map->add("file", HTMLInputElement::FILE); |
|
809 map->add("hidden", HTMLInputElement::HIDDEN); |
|
810 map->add("image", HTMLInputElement::IMAGE); |
|
811 map->add("khtml_isindex", HTMLInputElement::ISINDEX); |
|
812 map->add("month", HTMLInputElement::MONTH); |
|
813 map->add("number", HTMLInputElement::NUMBER); |
|
814 map->add("password", HTMLInputElement::PASSWORD); |
|
815 map->add("radio", HTMLInputElement::RADIO); |
|
816 map->add("range", HTMLInputElement::RANGE); |
|
817 map->add("reset", HTMLInputElement::RESET); |
|
818 map->add("search", HTMLInputElement::SEARCH); |
|
819 map->add("submit", HTMLInputElement::SUBMIT); |
|
820 map->add("tel", HTMLInputElement::TELEPHONE); |
|
821 map->add("time", HTMLInputElement::TIME); |
|
822 map->add("url", HTMLInputElement::URL); |
|
823 map->add("week", HTMLInputElement::WEEK); |
|
824 // No need to register "text" because it is the default type. |
|
825 return map; |
|
826 } |
|
827 |
|
828 void HTMLInputElement::setInputType(const String& t) |
|
829 { |
|
830 static const InputTypeMap* typeMap = createTypeMap(); |
|
831 InputType newType = t.isNull() ? TEXT : typeMap->get(t); |
|
832 |
|
833 // IMPORTANT: Don't allow the type to be changed to FILE after the first |
|
834 // type change, otherwise a JavaScript programmer would be able to set a text |
|
835 // field's value to something like /etc/passwd and then change it to a file field. |
|
836 if (inputType() != newType) { |
|
837 if (newType == FILE && m_haveType) |
|
838 // Set the attribute back to the old value. |
|
839 // Useful in case we were called from inside parseMappedAttribute. |
|
840 setAttribute(typeAttr, type()); |
|
841 else { |
|
842 checkedRadioButtons(this).removeButton(this); |
|
843 |
|
844 if (newType == FILE && !m_fileList) |
|
845 m_fileList = FileList::create(); |
|
846 |
|
847 bool wasAttached = attached(); |
|
848 if (wasAttached) |
|
849 detach(); |
|
850 |
|
851 bool didStoreValue = storesValueSeparateFromAttribute(); |
|
852 bool wasPasswordField = inputType() == PASSWORD; |
|
853 bool didRespectHeightAndWidth = respectHeightAndWidthAttrs(); |
|
854 m_type = newType; |
|
855 setNeedsWillValidateCheck(); |
|
856 bool willStoreValue = storesValueSeparateFromAttribute(); |
|
857 bool isPasswordField = inputType() == PASSWORD; |
|
858 bool willRespectHeightAndWidth = respectHeightAndWidthAttrs(); |
|
859 |
|
860 if (didStoreValue && !willStoreValue && !m_data.value().isNull()) { |
|
861 setAttribute(valueAttr, m_data.value()); |
|
862 m_data.setValue(String()); |
|
863 } |
|
864 if (!didStoreValue && willStoreValue) |
|
865 m_data.setValue(sanitizeValue(getAttribute(valueAttr))); |
|
866 else |
|
867 InputElement::updateValueIfNeeded(m_data, this); |
|
868 |
|
869 if (wasPasswordField && !isPasswordField) |
|
870 unregisterForActivationCallbackIfNeeded(); |
|
871 else if (!wasPasswordField && isPasswordField) |
|
872 registerForActivationCallbackIfNeeded(); |
|
873 |
|
874 if (didRespectHeightAndWidth != willRespectHeightAndWidth) { |
|
875 NamedNodeMap* map = attributeMap(); |
|
876 ASSERT(map); |
|
877 if (Attribute* height = map->getAttributeItem(heightAttr)) |
|
878 attributeChanged(height, false); |
|
879 if (Attribute* width = map->getAttributeItem(widthAttr)) |
|
880 attributeChanged(width, false); |
|
881 if (Attribute* align = map->getAttributeItem(alignAttr)) |
|
882 attributeChanged(align, false); |
|
883 } |
|
884 |
|
885 if (wasAttached) { |
|
886 attach(); |
|
887 if (document()->focusedNode() == this) |
|
888 updateFocusAppearance(true); |
|
889 } |
|
890 |
|
891 checkedRadioButtons(this).addButton(this); |
|
892 } |
|
893 |
|
894 setNeedsValidityCheck(); |
|
895 InputElement::notifyFormStateChanged(this); |
|
896 } |
|
897 m_haveType = true; |
|
898 |
|
899 if (inputType() != IMAGE && m_imageLoader) |
|
900 m_imageLoader.clear(); |
|
901 } |
|
902 |
|
903 static const AtomicString* createFormControlTypes() |
|
904 { |
|
905 AtomicString* types = new AtomicString[HTMLInputElement::numberOfTypes]; |
|
906 // The values must be lowercased because they will be the return values of |
|
907 // input.type and it must be lowercase according to DOM Level 2. |
|
908 types[HTMLInputElement::BUTTON] = "button"; |
|
909 types[HTMLInputElement::CHECKBOX] = "checkbox"; |
|
910 types[HTMLInputElement::COLOR] = "color"; |
|
911 types[HTMLInputElement::DATE] = "date"; |
|
912 types[HTMLInputElement::DATETIME] = "datetime"; |
|
913 types[HTMLInputElement::DATETIMELOCAL] = "datetime-local"; |
|
914 types[HTMLInputElement::EMAIL] = "email"; |
|
915 types[HTMLInputElement::FILE] = "file"; |
|
916 types[HTMLInputElement::HIDDEN] = "hidden"; |
|
917 types[HTMLInputElement::IMAGE] = "image"; |
|
918 types[HTMLInputElement::ISINDEX] = emptyAtom; |
|
919 types[HTMLInputElement::MONTH] = "month"; |
|
920 types[HTMLInputElement::NUMBER] = "number"; |
|
921 types[HTMLInputElement::PASSWORD] = "password"; |
|
922 types[HTMLInputElement::RADIO] = "radio"; |
|
923 types[HTMLInputElement::RANGE] = "range"; |
|
924 types[HTMLInputElement::RESET] = "reset"; |
|
925 types[HTMLInputElement::SEARCH] = "search"; |
|
926 types[HTMLInputElement::SUBMIT] = "submit"; |
|
927 types[HTMLInputElement::TELEPHONE] = "tel"; |
|
928 types[HTMLInputElement::TEXT] = "text"; |
|
929 types[HTMLInputElement::TIME] = "time"; |
|
930 types[HTMLInputElement::URL] = "url"; |
|
931 types[HTMLInputElement::WEEK] = "week"; |
|
932 return types; |
|
933 } |
|
934 |
|
935 const AtomicString& HTMLInputElement::formControlType() const |
|
936 { |
|
937 static const AtomicString* formControlTypes = createFormControlTypes(); |
|
938 return formControlTypes[inputType()]; |
|
939 } |
|
940 |
|
941 bool HTMLInputElement::saveFormControlState(String& result) const |
|
942 { |
|
943 switch (inputType()) { |
|
944 case BUTTON: |
|
945 case COLOR: |
|
946 case DATE: |
|
947 case DATETIME: |
|
948 case DATETIMELOCAL: |
|
949 case EMAIL: |
|
950 case FILE: |
|
951 case HIDDEN: |
|
952 case IMAGE: |
|
953 case ISINDEX: |
|
954 case MONTH: |
|
955 case NUMBER: |
|
956 case RANGE: |
|
957 case RESET: |
|
958 case SEARCH: |
|
959 case SUBMIT: |
|
960 case TELEPHONE: |
|
961 case TEXT: |
|
962 case TIME: |
|
963 case URL: |
|
964 case WEEK: { |
|
965 String currentValue = value(); |
|
966 if (currentValue == defaultValue()) |
|
967 return false; |
|
968 result = currentValue; |
|
969 return true; |
|
970 } |
|
971 case CHECKBOX: |
|
972 case RADIO: |
|
973 result = checked() ? "on" : "off"; |
|
974 return true; |
|
975 case PASSWORD: |
|
976 return false; |
|
977 } |
|
978 ASSERT_NOT_REACHED(); |
|
979 return false; |
|
980 } |
|
981 |
|
982 void HTMLInputElement::restoreFormControlState(const String& state) |
|
983 { |
|
984 ASSERT(inputType() != PASSWORD); // should never save/restore password fields |
|
985 switch (inputType()) { |
|
986 case BUTTON: |
|
987 case COLOR: |
|
988 case DATE: |
|
989 case DATETIME: |
|
990 case DATETIMELOCAL: |
|
991 case EMAIL: |
|
992 case FILE: |
|
993 case HIDDEN: |
|
994 case IMAGE: |
|
995 case ISINDEX: |
|
996 case MONTH: |
|
997 case NUMBER: |
|
998 case RANGE: |
|
999 case RESET: |
|
1000 case SEARCH: |
|
1001 case SUBMIT: |
|
1002 case TELEPHONE: |
|
1003 case TEXT: |
|
1004 case TIME: |
|
1005 case URL: |
|
1006 case WEEK: |
|
1007 setValue(state); |
|
1008 break; |
|
1009 case CHECKBOX: |
|
1010 case RADIO: |
|
1011 setChecked(state == "on"); |
|
1012 break; |
|
1013 case PASSWORD: |
|
1014 break; |
|
1015 } |
|
1016 } |
|
1017 |
|
1018 bool HTMLInputElement::canStartSelection() const |
|
1019 { |
|
1020 if (!isTextField()) |
|
1021 return false; |
|
1022 return HTMLFormControlElementWithState::canStartSelection(); |
|
1023 } |
|
1024 |
|
1025 bool HTMLInputElement::canHaveSelection() const |
|
1026 { |
|
1027 return isTextField(); |
|
1028 } |
|
1029 |
|
1030 void HTMLInputElement::accessKeyAction(bool sendToAnyElement) |
|
1031 { |
|
1032 switch (inputType()) { |
|
1033 case BUTTON: |
|
1034 case CHECKBOX: |
|
1035 case FILE: |
|
1036 case IMAGE: |
|
1037 case RADIO: |
|
1038 case RANGE: |
|
1039 case RESET: |
|
1040 case SUBMIT: |
|
1041 focus(false); |
|
1042 // send the mouse button events iff the caller specified sendToAnyElement |
|
1043 dispatchSimulatedClick(0, sendToAnyElement); |
|
1044 break; |
|
1045 case HIDDEN: |
|
1046 // a no-op for this type |
|
1047 break; |
|
1048 case COLOR: |
|
1049 case DATE: |
|
1050 case DATETIME: |
|
1051 case DATETIMELOCAL: |
|
1052 case EMAIL: |
|
1053 case ISINDEX: |
|
1054 case MONTH: |
|
1055 case NUMBER: |
|
1056 case PASSWORD: |
|
1057 case SEARCH: |
|
1058 case TELEPHONE: |
|
1059 case TEXT: |
|
1060 case TIME: |
|
1061 case URL: |
|
1062 case WEEK: |
|
1063 // should never restore previous selection here |
|
1064 focus(false); |
|
1065 break; |
|
1066 } |
|
1067 } |
|
1068 |
|
1069 bool HTMLInputElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const |
|
1070 { |
|
1071 if (((attrName == heightAttr || attrName == widthAttr) && respectHeightAndWidthAttrs()) |
|
1072 || attrName == vspaceAttr |
|
1073 || attrName == hspaceAttr) { |
|
1074 result = eUniversal; |
|
1075 return false; |
|
1076 } |
|
1077 |
|
1078 if (attrName == alignAttr) { |
|
1079 if (inputType() == IMAGE) { |
|
1080 // Share with <img> since the alignment behavior is the same. |
|
1081 result = eReplaced; |
|
1082 return false; |
|
1083 } |
|
1084 } |
|
1085 |
|
1086 return HTMLElement::mapToEntry(attrName, result); |
|
1087 } |
|
1088 |
|
1089 void HTMLInputElement::parseMappedAttribute(Attribute* attr) |
|
1090 { |
|
1091 if (attr->name() == nameAttr) { |
|
1092 checkedRadioButtons(this).removeButton(this); |
|
1093 m_data.setName(attr->value()); |
|
1094 checkedRadioButtons(this).addButton(this); |
|
1095 HTMLFormControlElementWithState::parseMappedAttribute(attr); |
|
1096 } else if (attr->name() == autocompleteAttr) { |
|
1097 if (equalIgnoringCase(attr->value(), "off")) { |
|
1098 m_autocomplete = Off; |
|
1099 registerForActivationCallbackIfNeeded(); |
|
1100 } else { |
|
1101 bool needsToUnregister = m_autocomplete == Off; |
|
1102 |
|
1103 if (attr->isEmpty()) |
|
1104 m_autocomplete = Uninitialized; |
|
1105 else |
|
1106 m_autocomplete = On; |
|
1107 |
|
1108 if (needsToUnregister) |
|
1109 unregisterForActivationCallbackIfNeeded(); |
|
1110 } |
|
1111 } else if (attr->name() == typeAttr) { |
|
1112 setInputType(attr->value()); |
|
1113 } else if (attr->name() == valueAttr) { |
|
1114 // We only need to setChanged if the form is looking at the default value right now. |
|
1115 if (m_data.value().isNull()) |
|
1116 setNeedsStyleRecalc(); |
|
1117 setFormControlValueMatchesRenderer(false); |
|
1118 setNeedsValidityCheck(); |
|
1119 } else if (attr->name() == checkedAttr) { |
|
1120 m_defaultChecked = !attr->isNull(); |
|
1121 if (m_useDefaultChecked) { |
|
1122 setChecked(m_defaultChecked); |
|
1123 m_useDefaultChecked = true; |
|
1124 } |
|
1125 setNeedsValidityCheck(); |
|
1126 } else if (attr->name() == maxlengthAttr) { |
|
1127 InputElement::parseMaxLengthAttribute(m_data, this, this, attr); |
|
1128 setNeedsValidityCheck(); |
|
1129 } else if (attr->name() == sizeAttr) |
|
1130 InputElement::parseSizeAttribute(m_data, this, attr); |
|
1131 else if (attr->name() == altAttr) { |
|
1132 if (renderer() && inputType() == IMAGE) |
|
1133 toRenderImage(renderer())->updateAltText(); |
|
1134 } else if (attr->name() == srcAttr) { |
|
1135 if (renderer() && inputType() == IMAGE) { |
|
1136 if (!m_imageLoader) |
|
1137 m_imageLoader.set(new HTMLImageLoader(this)); |
|
1138 m_imageLoader->updateFromElementIgnoringPreviousError(); |
|
1139 } |
|
1140 } else if (attr->name() == usemapAttr || attr->name() == accesskeyAttr) { |
|
1141 // FIXME: ignore for the moment |
|
1142 } else if (attr->name() == vspaceAttr) { |
|
1143 addCSSLength(attr, CSSPropertyMarginTop, attr->value()); |
|
1144 addCSSLength(attr, CSSPropertyMarginBottom, attr->value()); |
|
1145 } else if (attr->name() == hspaceAttr) { |
|
1146 addCSSLength(attr, CSSPropertyMarginLeft, attr->value()); |
|
1147 addCSSLength(attr, CSSPropertyMarginRight, attr->value()); |
|
1148 } else if (attr->name() == alignAttr) { |
|
1149 if (inputType() == IMAGE) |
|
1150 addHTMLAlignment(attr); |
|
1151 } else if (attr->name() == widthAttr) { |
|
1152 if (respectHeightAndWidthAttrs()) |
|
1153 addCSSLength(attr, CSSPropertyWidth, attr->value()); |
|
1154 } else if (attr->name() == heightAttr) { |
|
1155 if (respectHeightAndWidthAttrs()) |
|
1156 addCSSLength(attr, CSSPropertyHeight, attr->value()); |
|
1157 } else if (attr->name() == onsearchAttr) { |
|
1158 // Search field and slider attributes all just cause updateFromElement to be called through style recalcing. |
|
1159 setAttributeEventListener(eventNames().searchEvent, createAttributeEventListener(this, attr)); |
|
1160 } else if (attr->name() == resultsAttr) { |
|
1161 int oldResults = m_maxResults; |
|
1162 m_maxResults = !attr->isNull() ? std::min(attr->value().toInt(), maxSavedResults) : -1; |
|
1163 // FIXME: Detaching just for maxResults change is not ideal. We should figure out the right |
|
1164 // time to relayout for this change. |
|
1165 if (m_maxResults != oldResults && (m_maxResults <= 0 || oldResults <= 0) && attached()) { |
|
1166 detach(); |
|
1167 attach(); |
|
1168 } |
|
1169 setNeedsStyleRecalc(); |
|
1170 } else if (attr->name() == autosaveAttr |
|
1171 || attr->name() == incrementalAttr) |
|
1172 setNeedsStyleRecalc(); |
|
1173 else if (attr->name() == minAttr |
|
1174 || attr->name() == maxAttr) { |
|
1175 if (inputType() == RANGE) { |
|
1176 // Sanitize the value. |
|
1177 setValue(value()); |
|
1178 setNeedsStyleRecalc(); |
|
1179 } |
|
1180 setNeedsValidityCheck(); |
|
1181 } else if (attr->name() == multipleAttr |
|
1182 || attr->name() == patternAttr |
|
1183 || attr->name() == precisionAttr |
|
1184 || attr->name() == stepAttr) |
|
1185 setNeedsValidityCheck(); |
|
1186 #if ENABLE(DATALIST) |
|
1187 else if (attr->name() == listAttr) |
|
1188 m_hasNonEmptyList = !attr->isEmpty(); |
|
1189 // FIXME: we need to tell this change to a renderer if the attribute affects the appearance. |
|
1190 #endif |
|
1191 #if ENABLE(INPUT_SPEECH) |
|
1192 else if (attr->name() == speechAttr) { |
|
1193 if (renderer()) |
|
1194 renderer()->updateFromElement(); |
|
1195 setNeedsStyleRecalc(); |
|
1196 } |
|
1197 #endif |
|
1198 else |
|
1199 HTMLTextFormControlElement::parseMappedAttribute(attr); |
|
1200 } |
|
1201 |
|
1202 bool HTMLInputElement::rendererIsNeeded(RenderStyle *style) |
|
1203 { |
|
1204 if (inputType() == HIDDEN) |
|
1205 return false; |
|
1206 return HTMLFormControlElementWithState::rendererIsNeeded(style); |
|
1207 } |
|
1208 |
|
1209 RenderObject* HTMLInputElement::createRenderer(RenderArena *arena, RenderStyle *style) |
|
1210 { |
|
1211 switch (inputType()) { |
|
1212 case BUTTON: |
|
1213 case RESET: |
|
1214 case SUBMIT: |
|
1215 return new (arena) RenderButton(this); |
|
1216 case CHECKBOX: |
|
1217 case RADIO: |
|
1218 return RenderObject::createObject(this, style); |
|
1219 case FILE: |
|
1220 return new (arena) RenderFileUploadControl(this); |
|
1221 case HIDDEN: |
|
1222 break; |
|
1223 case IMAGE: |
|
1224 return new (arena) RenderImage(this); |
|
1225 case RANGE: |
|
1226 return new (arena) RenderSlider(this); |
|
1227 case COLOR: |
|
1228 case DATE: |
|
1229 case DATETIME: |
|
1230 case DATETIMELOCAL: |
|
1231 case EMAIL: |
|
1232 case ISINDEX: |
|
1233 case MONTH: |
|
1234 case NUMBER: |
|
1235 case PASSWORD: |
|
1236 case SEARCH: |
|
1237 case TELEPHONE: |
|
1238 case TEXT: |
|
1239 case TIME: |
|
1240 case URL: |
|
1241 case WEEK: |
|
1242 return new (arena) RenderTextControlSingleLine(this, placeholderShouldBeVisible()); |
|
1243 } |
|
1244 ASSERT(false); |
|
1245 return 0; |
|
1246 } |
|
1247 |
|
1248 void HTMLInputElement::attach() |
|
1249 { |
|
1250 if (!m_inited) { |
|
1251 if (!m_haveType) |
|
1252 setInputType(getAttribute(typeAttr)); |
|
1253 m_inited = true; |
|
1254 } |
|
1255 |
|
1256 HTMLFormControlElementWithState::attach(); |
|
1257 |
|
1258 if (inputType() == IMAGE) { |
|
1259 if (!m_imageLoader) |
|
1260 m_imageLoader.set(new HTMLImageLoader(this)); |
|
1261 m_imageLoader->updateFromElement(); |
|
1262 if (renderer() && m_imageLoader->haveFiredBeforeLoadEvent()) { |
|
1263 RenderImage* imageObj = toRenderImage(renderer()); |
|
1264 imageObj->setCachedImage(m_imageLoader->image()); |
|
1265 |
|
1266 // If we have no image at all because we have no src attribute, set |
|
1267 // image height and width for the alt text instead. |
|
1268 if (!m_imageLoader->image() && !imageObj->cachedImage()) |
|
1269 imageObj->setImageSizeForAltText(); |
|
1270 } |
|
1271 } |
|
1272 |
|
1273 if (inputType() == RADIO) |
|
1274 updateCheckedRadioButtons(); |
|
1275 |
|
1276 if (document()->focusedNode() == this) |
|
1277 document()->updateFocusAppearanceSoon(true /* restore selection */); |
|
1278 } |
|
1279 |
|
1280 void HTMLInputElement::detach() |
|
1281 { |
|
1282 HTMLFormControlElementWithState::detach(); |
|
1283 setFormControlValueMatchesRenderer(false); |
|
1284 } |
|
1285 |
|
1286 String HTMLInputElement::altText() const |
|
1287 { |
|
1288 // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen |
|
1289 // also heavily discussed by Hixie on bugzilla |
|
1290 // note this is intentionally different to HTMLImageElement::altText() |
|
1291 String alt = getAttribute(altAttr); |
|
1292 // fall back to title attribute |
|
1293 if (alt.isNull()) |
|
1294 alt = getAttribute(titleAttr); |
|
1295 if (alt.isNull()) |
|
1296 alt = getAttribute(valueAttr); |
|
1297 if (alt.isEmpty()) |
|
1298 alt = inputElementAltText(); |
|
1299 return alt; |
|
1300 } |
|
1301 |
|
1302 bool HTMLInputElement::isSuccessfulSubmitButton() const |
|
1303 { |
|
1304 // HTML spec says that buttons must have names to be considered successful. |
|
1305 // However, other browsers do not impose this constraint. So we do likewise. |
|
1306 return !disabled() && (inputType() == IMAGE || inputType() == SUBMIT); |
|
1307 } |
|
1308 |
|
1309 bool HTMLInputElement::isActivatedSubmit() const |
|
1310 { |
|
1311 return m_activeSubmit; |
|
1312 } |
|
1313 |
|
1314 void HTMLInputElement::setActivatedSubmit(bool flag) |
|
1315 { |
|
1316 m_activeSubmit = flag; |
|
1317 } |
|
1318 |
|
1319 bool HTMLInputElement::appendFormData(FormDataList& encoding, bool multipart) |
|
1320 { |
|
1321 // image generates its own names, but for other types there is no form data unless there's a name |
|
1322 if (name().isEmpty() && inputType() != IMAGE) |
|
1323 return false; |
|
1324 |
|
1325 switch (inputType()) { |
|
1326 case COLOR: |
|
1327 case DATE: |
|
1328 case DATETIME: |
|
1329 case DATETIMELOCAL: |
|
1330 case EMAIL: |
|
1331 case HIDDEN: |
|
1332 case ISINDEX: |
|
1333 case MONTH: |
|
1334 case NUMBER: |
|
1335 case PASSWORD: |
|
1336 case RANGE: |
|
1337 case SEARCH: |
|
1338 case TELEPHONE: |
|
1339 case TEXT: |
|
1340 case TIME: |
|
1341 case URL: |
|
1342 case WEEK: |
|
1343 // always successful |
|
1344 encoding.appendData(name(), value()); |
|
1345 return true; |
|
1346 |
|
1347 case CHECKBOX: |
|
1348 case RADIO: |
|
1349 if (checked()) { |
|
1350 encoding.appendData(name(), value()); |
|
1351 return true; |
|
1352 } |
|
1353 break; |
|
1354 |
|
1355 case BUTTON: |
|
1356 case RESET: |
|
1357 // these types of buttons are never successful |
|
1358 return false; |
|
1359 |
|
1360 case IMAGE: |
|
1361 if (m_activeSubmit) { |
|
1362 encoding.appendData(name().isEmpty() ? "x" : (name() + ".x"), m_xPos); |
|
1363 encoding.appendData(name().isEmpty() ? "y" : (name() + ".y"), m_yPos); |
|
1364 if (!name().isEmpty() && !value().isEmpty()) |
|
1365 encoding.appendData(name(), value()); |
|
1366 return true; |
|
1367 } |
|
1368 break; |
|
1369 |
|
1370 case SUBMIT: |
|
1371 if (m_activeSubmit) { |
|
1372 String encstr = valueWithDefault(); |
|
1373 encoding.appendData(name(), encstr); |
|
1374 return true; |
|
1375 } |
|
1376 break; |
|
1377 |
|
1378 case FILE: { |
|
1379 unsigned numFiles = m_fileList->length(); |
|
1380 if (!multipart) { |
|
1381 // Send only the basenames. |
|
1382 // 4.10.16.4 and 4.10.16.6 sections in HTML5. |
|
1383 |
|
1384 // Unlike the multipart case, we have no special |
|
1385 // handling for the empty fileList because Netscape |
|
1386 // doesn't support for non-multipart submission of |
|
1387 // file inputs, and Firefox doesn't add "name=" query |
|
1388 // parameter. |
|
1389 |
|
1390 for (unsigned i = 0; i < numFiles; ++i) |
|
1391 encoding.appendData(name(), m_fileList->item(i)->fileName()); |
|
1392 return true; |
|
1393 } |
|
1394 |
|
1395 // If no filename at all is entered, return successful but empty. |
|
1396 // Null would be more logical, but Netscape posts an empty file. Argh. |
|
1397 if (!numFiles) { |
|
1398 encoding.appendBlob(name(), File::create("")); |
|
1399 return true; |
|
1400 } |
|
1401 |
|
1402 for (unsigned i = 0; i < numFiles; ++i) |
|
1403 encoding.appendBlob(name(), m_fileList->item(i)); |
|
1404 return true; |
|
1405 } |
|
1406 } |
|
1407 return false; |
|
1408 } |
|
1409 |
|
1410 void HTMLInputElement::reset() |
|
1411 { |
|
1412 if (storesValueSeparateFromAttribute()) |
|
1413 setValue(String()); |
|
1414 |
|
1415 setChecked(m_defaultChecked); |
|
1416 m_useDefaultChecked = true; |
|
1417 } |
|
1418 |
|
1419 bool HTMLInputElement::isTextField() const |
|
1420 { |
|
1421 switch (inputType()) { |
|
1422 case COLOR: |
|
1423 case DATE: |
|
1424 case DATETIME: |
|
1425 case DATETIMELOCAL: |
|
1426 case EMAIL: |
|
1427 case ISINDEX: |
|
1428 case MONTH: |
|
1429 case NUMBER: |
|
1430 case PASSWORD: |
|
1431 case SEARCH: |
|
1432 case TELEPHONE: |
|
1433 case TEXT: |
|
1434 case TIME: |
|
1435 case URL: |
|
1436 case WEEK: |
|
1437 return true; |
|
1438 case BUTTON: |
|
1439 case CHECKBOX: |
|
1440 case FILE: |
|
1441 case HIDDEN: |
|
1442 case IMAGE: |
|
1443 case RADIO: |
|
1444 case RANGE: |
|
1445 case RESET: |
|
1446 case SUBMIT: |
|
1447 return false; |
|
1448 } |
|
1449 ASSERT_NOT_REACHED(); |
|
1450 return false; |
|
1451 } |
|
1452 |
|
1453 void HTMLInputElement::setChecked(bool nowChecked, bool sendChangeEvent) |
|
1454 { |
|
1455 if (checked() == nowChecked) |
|
1456 return; |
|
1457 |
|
1458 checkedRadioButtons(this).removeButton(this); |
|
1459 |
|
1460 m_useDefaultChecked = false; |
|
1461 m_checked = nowChecked; |
|
1462 setNeedsStyleRecalc(); |
|
1463 |
|
1464 updateCheckedRadioButtons(); |
|
1465 |
|
1466 // Ideally we'd do this from the render tree (matching |
|
1467 // RenderTextView), but it's not possible to do it at the moment |
|
1468 // because of the way the code is structured. |
|
1469 if (renderer() && AXObjectCache::accessibilityEnabled()) |
|
1470 renderer()->document()->axObjectCache()->postNotification(renderer(), AXObjectCache::AXCheckedStateChanged, true); |
|
1471 |
|
1472 // Only send a change event for items in the document (avoid firing during |
|
1473 // parsing) and don't send a change event for a radio button that's getting |
|
1474 // unchecked to match other browsers. DOM is not a useful standard for this |
|
1475 // because it says only to fire change events at "lose focus" time, which is |
|
1476 // definitely wrong in practice for these types of elements. |
|
1477 if (sendChangeEvent && inDocument() && (inputType() != RADIO || nowChecked)) |
|
1478 dispatchFormControlChangeEvent(); |
|
1479 } |
|
1480 |
|
1481 void HTMLInputElement::setIndeterminate(bool newValue) |
|
1482 { |
|
1483 // Only checkboxes and radio buttons honor indeterminate. |
|
1484 if (!allowsIndeterminate() || indeterminate() == newValue) |
|
1485 return; |
|
1486 |
|
1487 m_indeterminate = newValue; |
|
1488 |
|
1489 setNeedsStyleRecalc(); |
|
1490 |
|
1491 if (renderer() && renderer()->style()->hasAppearance()) |
|
1492 renderer()->theme()->stateChanged(renderer(), CheckedState); |
|
1493 } |
|
1494 |
|
1495 int HTMLInputElement::size() const |
|
1496 { |
|
1497 return m_data.size(); |
|
1498 } |
|
1499 |
|
1500 void HTMLInputElement::copyNonAttributeProperties(const Element* source) |
|
1501 { |
|
1502 const HTMLInputElement* sourceElement = static_cast<const HTMLInputElement*>(source); |
|
1503 |
|
1504 m_data.setValue(sourceElement->m_data.value()); |
|
1505 setChecked(sourceElement->m_checked); |
|
1506 m_defaultChecked = sourceElement->m_defaultChecked; |
|
1507 m_useDefaultChecked = sourceElement->m_useDefaultChecked; |
|
1508 m_indeterminate = sourceElement->m_indeterminate; |
|
1509 |
|
1510 HTMLFormControlElementWithState::copyNonAttributeProperties(source); |
|
1511 } |
|
1512 |
|
1513 String HTMLInputElement::value() const |
|
1514 { |
|
1515 // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't applicable to the file upload control |
|
1516 // but we don't want to break existing websites, who may be relying on being able to get the file name as a value. |
|
1517 if (inputType() == FILE) { |
|
1518 if (!m_fileList->isEmpty()) |
|
1519 return m_fileList->item(0)->fileName(); |
|
1520 return String(); |
|
1521 } |
|
1522 |
|
1523 String value = m_data.value(); |
|
1524 if (value.isNull()) { |
|
1525 value = sanitizeValue(fastGetAttribute(valueAttr)); |
|
1526 |
|
1527 // If no attribute exists, extra handling may be necessary. |
|
1528 // For Checkbox Types just use "on" or "" based off the checked() state of the control. |
|
1529 // For a Range Input use the calculated default value. |
|
1530 if (value.isNull()) { |
|
1531 if (inputType() == CHECKBOX || inputType() == RADIO) |
|
1532 return checked() ? "on" : ""; |
|
1533 if (inputType() == RANGE) |
|
1534 return serializeForNumberType(StepRange(this).defaultValue()); |
|
1535 } |
|
1536 } |
|
1537 |
|
1538 return value; |
|
1539 } |
|
1540 |
|
1541 String HTMLInputElement::valueWithDefault() const |
|
1542 { |
|
1543 String v = value(); |
|
1544 if (v.isNull()) { |
|
1545 switch (inputType()) { |
|
1546 case BUTTON: |
|
1547 case CHECKBOX: |
|
1548 case COLOR: |
|
1549 case DATE: |
|
1550 case DATETIME: |
|
1551 case DATETIMELOCAL: |
|
1552 case EMAIL: |
|
1553 case FILE: |
|
1554 case HIDDEN: |
|
1555 case IMAGE: |
|
1556 case ISINDEX: |
|
1557 case MONTH: |
|
1558 case NUMBER: |
|
1559 case PASSWORD: |
|
1560 case RADIO: |
|
1561 case RANGE: |
|
1562 case SEARCH: |
|
1563 case TELEPHONE: |
|
1564 case TEXT: |
|
1565 case TIME: |
|
1566 case URL: |
|
1567 case WEEK: |
|
1568 break; |
|
1569 case RESET: |
|
1570 v = resetButtonDefaultLabel(); |
|
1571 break; |
|
1572 case SUBMIT: |
|
1573 v = submitButtonDefaultLabel(); |
|
1574 break; |
|
1575 } |
|
1576 } |
|
1577 return v; |
|
1578 } |
|
1579 |
|
1580 void HTMLInputElement::setValueForUser(const String& value) |
|
1581 { |
|
1582 // Call setValue and make it send a change event. |
|
1583 setValue(value, true); |
|
1584 } |
|
1585 |
|
1586 const String& HTMLInputElement::suggestedValue() const |
|
1587 { |
|
1588 return m_data.suggestedValue(); |
|
1589 } |
|
1590 |
|
1591 void HTMLInputElement::setSuggestedValue(const String& value) |
|
1592 { |
|
1593 if (inputType() != TEXT) |
|
1594 return; |
|
1595 setFormControlValueMatchesRenderer(false); |
|
1596 m_data.setSuggestedValue(sanitizeValue(value)); |
|
1597 updatePlaceholderVisibility(false); |
|
1598 if (renderer()) |
|
1599 renderer()->updateFromElement(); |
|
1600 setNeedsStyleRecalc(); |
|
1601 } |
|
1602 |
|
1603 void HTMLInputElement::setValue(const String& value, bool sendChangeEvent) |
|
1604 { |
|
1605 // For security reasons, we don't allow setting the filename, but we do allow clearing it. |
|
1606 // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't applicable to the file upload control |
|
1607 // but we don't want to break existing websites, who may be relying on this method to clear things. |
|
1608 if (inputType() == FILE && !value.isEmpty()) |
|
1609 return; |
|
1610 |
|
1611 setFormControlValueMatchesRenderer(false); |
|
1612 if (storesValueSeparateFromAttribute()) { |
|
1613 if (inputType() == FILE) |
|
1614 m_fileList->clear(); |
|
1615 else { |
|
1616 m_data.setValue(sanitizeValue(value)); |
|
1617 if (isTextField()) |
|
1618 updatePlaceholderVisibility(false); |
|
1619 } |
|
1620 setNeedsStyleRecalc(); |
|
1621 } else |
|
1622 setAttribute(valueAttr, sanitizeValue(value)); |
|
1623 |
|
1624 setNeedsValidityCheck(); |
|
1625 |
|
1626 if (isTextField()) { |
|
1627 unsigned max = m_data.value().length(); |
|
1628 if (document()->focusedNode() == this) |
|
1629 InputElement::updateSelectionRange(this, this, max, max); |
|
1630 else |
|
1631 cacheSelection(max, max); |
|
1632 m_data.setSuggestedValue(String()); |
|
1633 } |
|
1634 |
|
1635 // Don't dispatch the change event when focused, it will be dispatched |
|
1636 // when the control loses focus. |
|
1637 if (sendChangeEvent && document()->focusedNode() != this) |
|
1638 dispatchFormControlChangeEvent(); |
|
1639 |
|
1640 InputElement::notifyFormStateChanged(this); |
|
1641 } |
|
1642 |
|
1643 double HTMLInputElement::parseToDouble(const String& src, double defaultValue) const |
|
1644 { |
|
1645 switch (inputType()) { |
|
1646 case DATE: |
|
1647 case DATETIME: |
|
1648 case DATETIMELOCAL: |
|
1649 case TIME: |
|
1650 case WEEK: { |
|
1651 DateComponents date; |
|
1652 if (!parseToDateComponents(inputType(), src, &date)) |
|
1653 return defaultValue; |
|
1654 double msec = date.millisecondsSinceEpoch(); |
|
1655 ASSERT(isfinite(msec)); |
|
1656 return msec; |
|
1657 } |
|
1658 case MONTH: { |
|
1659 DateComponents date; |
|
1660 if (!parseToDateComponents(inputType(), src, &date)) |
|
1661 return defaultValue; |
|
1662 double months = date.monthsSinceEpoch(); |
|
1663 ASSERT(isfinite(months)); |
|
1664 return months; |
|
1665 } |
|
1666 case NUMBER: |
|
1667 case RANGE: { |
|
1668 double numberValue; |
|
1669 if (!parseToDoubleForNumberType(src, &numberValue)) |
|
1670 return defaultValue; |
|
1671 ASSERT(isfinite(numberValue)); |
|
1672 return numberValue; |
|
1673 } |
|
1674 |
|
1675 case BUTTON: |
|
1676 case CHECKBOX: |
|
1677 case COLOR: |
|
1678 case EMAIL: |
|
1679 case FILE: |
|
1680 case HIDDEN: |
|
1681 case IMAGE: |
|
1682 case ISINDEX: |
|
1683 case PASSWORD: |
|
1684 case RADIO: |
|
1685 case RESET: |
|
1686 case SEARCH: |
|
1687 case SUBMIT: |
|
1688 case TELEPHONE: |
|
1689 case TEXT: |
|
1690 case URL: |
|
1691 return defaultValue; |
|
1692 } |
|
1693 ASSERT_NOT_REACHED(); |
|
1694 return defaultValue; |
|
1695 } |
|
1696 |
|
1697 double HTMLInputElement::valueAsDate() const |
|
1698 { |
|
1699 switch (inputType()) { |
|
1700 case DATE: |
|
1701 case DATETIME: |
|
1702 case TIME: |
|
1703 case WEEK: |
|
1704 return parseToDouble(value(), DateComponents::invalidMilliseconds()); |
|
1705 case MONTH: { |
|
1706 DateComponents date; |
|
1707 if (!parseToDateComponents(inputType(), value(), &date)) |
|
1708 return DateComponents::invalidMilliseconds(); |
|
1709 double msec = date.millisecondsSinceEpoch(); |
|
1710 ASSERT(isfinite(msec)); |
|
1711 return msec; |
|
1712 } |
|
1713 |
|
1714 case BUTTON: |
|
1715 case CHECKBOX: |
|
1716 case COLOR: |
|
1717 case DATETIMELOCAL: // valueAsDate doesn't work for the DATETIMELOCAL type according to the standard. |
|
1718 case EMAIL: |
|
1719 case FILE: |
|
1720 case HIDDEN: |
|
1721 case IMAGE: |
|
1722 case ISINDEX: |
|
1723 case NUMBER: |
|
1724 case PASSWORD: |
|
1725 case RADIO: |
|
1726 case RANGE: |
|
1727 case RESET: |
|
1728 case SEARCH: |
|
1729 case SUBMIT: |
|
1730 case TELEPHONE: |
|
1731 case TEXT: |
|
1732 case URL: |
|
1733 return DateComponents::invalidMilliseconds(); |
|
1734 } |
|
1735 ASSERT_NOT_REACHED(); |
|
1736 return DateComponents::invalidMilliseconds(); |
|
1737 } |
|
1738 |
|
1739 void HTMLInputElement::setValueAsDate(double value, ExceptionCode& ec) |
|
1740 { |
|
1741 switch (inputType()) { |
|
1742 case DATE: |
|
1743 case DATETIME: |
|
1744 case TIME: |
|
1745 case WEEK: |
|
1746 setValue(serializeForDateTimeTypes(value)); |
|
1747 return; |
|
1748 case MONTH: { |
|
1749 DateComponents date; |
|
1750 if (!date.setMillisecondsSinceEpochForMonth(value)) { |
|
1751 setValue(String()); |
|
1752 return; |
|
1753 } |
|
1754 setValue(date.toString()); |
|
1755 return; |
|
1756 } |
|
1757 case BUTTON: |
|
1758 case CHECKBOX: |
|
1759 case COLOR: |
|
1760 case DATETIMELOCAL: // valueAsDate doesn't work for the DATETIMELOCAL type according to the standard. |
|
1761 case EMAIL: |
|
1762 case FILE: |
|
1763 case HIDDEN: |
|
1764 case IMAGE: |
|
1765 case ISINDEX: |
|
1766 case NUMBER: |
|
1767 case PASSWORD: |
|
1768 case RADIO: |
|
1769 case RANGE: |
|
1770 case RESET: |
|
1771 case SEARCH: |
|
1772 case SUBMIT: |
|
1773 case TELEPHONE: |
|
1774 case TEXT: |
|
1775 case URL: |
|
1776 ec = INVALID_STATE_ERR; |
|
1777 return; |
|
1778 } |
|
1779 ASSERT_NOT_REACHED(); |
|
1780 } |
|
1781 |
|
1782 double HTMLInputElement::valueAsNumber() const |
|
1783 { |
|
1784 const double nan = numeric_limits<double>::quiet_NaN(); |
|
1785 switch (inputType()) { |
|
1786 case DATE: |
|
1787 case DATETIME: |
|
1788 case DATETIMELOCAL: |
|
1789 case MONTH: |
|
1790 case NUMBER: |
|
1791 case RANGE: |
|
1792 case TIME: |
|
1793 case WEEK: |
|
1794 return parseToDouble(value(), nan); |
|
1795 |
|
1796 case BUTTON: |
|
1797 case CHECKBOX: |
|
1798 case COLOR: |
|
1799 case EMAIL: |
|
1800 case FILE: |
|
1801 case HIDDEN: |
|
1802 case IMAGE: |
|
1803 case ISINDEX: |
|
1804 case PASSWORD: |
|
1805 case RADIO: |
|
1806 case RESET: |
|
1807 case SEARCH: |
|
1808 case SUBMIT: |
|
1809 case TELEPHONE: |
|
1810 case TEXT: |
|
1811 case URL: |
|
1812 return nan; |
|
1813 } |
|
1814 ASSERT_NOT_REACHED(); |
|
1815 return nan; |
|
1816 } |
|
1817 |
|
1818 void HTMLInputElement::setValueAsNumber(double newValue, ExceptionCode& ec) |
|
1819 { |
|
1820 if (!isfinite(newValue)) { |
|
1821 ec = NOT_SUPPORTED_ERR; |
|
1822 return; |
|
1823 } |
|
1824 switch (inputType()) { |
|
1825 case DATE: |
|
1826 case DATETIME: |
|
1827 case DATETIMELOCAL: |
|
1828 case MONTH: |
|
1829 case NUMBER: |
|
1830 case RANGE: |
|
1831 case TIME: |
|
1832 case WEEK: |
|
1833 setValue(serialize(newValue)); |
|
1834 return; |
|
1835 |
|
1836 case BUTTON: |
|
1837 case CHECKBOX: |
|
1838 case COLOR: |
|
1839 case EMAIL: |
|
1840 case FILE: |
|
1841 case HIDDEN: |
|
1842 case IMAGE: |
|
1843 case ISINDEX: |
|
1844 case PASSWORD: |
|
1845 case RADIO: |
|
1846 case RESET: |
|
1847 case SEARCH: |
|
1848 case SUBMIT: |
|
1849 case TELEPHONE: |
|
1850 case TEXT: |
|
1851 case URL: |
|
1852 ec = INVALID_STATE_ERR; |
|
1853 return; |
|
1854 } |
|
1855 ASSERT_NOT_REACHED(); |
|
1856 } |
|
1857 |
|
1858 String HTMLInputElement::serializeForDateTimeTypes(double value) const |
|
1859 { |
|
1860 bool success = false; |
|
1861 DateComponents date; |
|
1862 switch (inputType()) { |
|
1863 case DATE: |
|
1864 success = date.setMillisecondsSinceEpochForDate(value); |
|
1865 break; |
|
1866 case DATETIME: |
|
1867 success = date.setMillisecondsSinceEpochForDateTime(value); |
|
1868 break; |
|
1869 case DATETIMELOCAL: |
|
1870 success = date.setMillisecondsSinceEpochForDateTimeLocal(value); |
|
1871 break; |
|
1872 case MONTH: |
|
1873 success = date.setMonthsSinceEpoch(value); |
|
1874 break; |
|
1875 case TIME: |
|
1876 success = date.setMillisecondsSinceMidnight(value); |
|
1877 break; |
|
1878 case WEEK: |
|
1879 success = date.setMillisecondsSinceEpochForWeek(value); |
|
1880 break; |
|
1881 case NUMBER: |
|
1882 case RANGE: |
|
1883 case BUTTON: |
|
1884 case CHECKBOX: |
|
1885 case COLOR: |
|
1886 case EMAIL: |
|
1887 case FILE: |
|
1888 case HIDDEN: |
|
1889 case IMAGE: |
|
1890 case ISINDEX: |
|
1891 case PASSWORD: |
|
1892 case RADIO: |
|
1893 case RESET: |
|
1894 case SEARCH: |
|
1895 case SUBMIT: |
|
1896 case TELEPHONE: |
|
1897 case TEXT: |
|
1898 case URL: |
|
1899 ASSERT_NOT_REACHED(); |
|
1900 return String(); |
|
1901 } |
|
1902 if (!success) |
|
1903 return String(); |
|
1904 |
|
1905 double step; |
|
1906 if (!getAllowedValueStep(&step)) |
|
1907 return date.toString(); |
|
1908 if (!fmod(step, msecPerMinute)) |
|
1909 return date.toString(DateComponents::None); |
|
1910 if (!fmod(step, msecPerSecond)) |
|
1911 return date.toString(DateComponents::Second); |
|
1912 return date.toString(DateComponents::Millisecond); |
|
1913 } |
|
1914 |
|
1915 String HTMLInputElement::serialize(double value) const |
|
1916 { |
|
1917 if (!isfinite(value)) |
|
1918 return String(); |
|
1919 switch (inputType()) { |
|
1920 case DATE: |
|
1921 case DATETIME: |
|
1922 case DATETIMELOCAL: |
|
1923 case MONTH: |
|
1924 case TIME: |
|
1925 case WEEK: |
|
1926 return serializeForDateTimeTypes(value); |
|
1927 case NUMBER: |
|
1928 case RANGE: |
|
1929 return serializeForNumberType(value); |
|
1930 |
|
1931 case BUTTON: |
|
1932 case CHECKBOX: |
|
1933 case COLOR: |
|
1934 case EMAIL: |
|
1935 case FILE: |
|
1936 case HIDDEN: |
|
1937 case IMAGE: |
|
1938 case ISINDEX: |
|
1939 case PASSWORD: |
|
1940 case RADIO: |
|
1941 case RESET: |
|
1942 case SEARCH: |
|
1943 case SUBMIT: |
|
1944 case TELEPHONE: |
|
1945 case TEXT: |
|
1946 case URL: |
|
1947 break; |
|
1948 } |
|
1949 ASSERT_NOT_REACHED(); |
|
1950 return String(); |
|
1951 } |
|
1952 |
|
1953 String HTMLInputElement::placeholder() const |
|
1954 { |
|
1955 return getAttribute(placeholderAttr).string(); |
|
1956 } |
|
1957 |
|
1958 void HTMLInputElement::setPlaceholder(const String& value) |
|
1959 { |
|
1960 setAttribute(placeholderAttr, value); |
|
1961 } |
|
1962 |
|
1963 bool HTMLInputElement::searchEventsShouldBeDispatched() const |
|
1964 { |
|
1965 return hasAttribute(incrementalAttr); |
|
1966 } |
|
1967 |
|
1968 void HTMLInputElement::setValueFromRenderer(const String& value) |
|
1969 { |
|
1970 // File upload controls will always use setFileListFromRenderer. |
|
1971 ASSERT(inputType() != FILE); |
|
1972 m_data.setSuggestedValue(String()); |
|
1973 updatePlaceholderVisibility(false); |
|
1974 InputElement::setValueFromRenderer(m_data, this, this, value); |
|
1975 setNeedsValidityCheck(); |
|
1976 |
|
1977 // Clear autofill flag (and yellow background) on user edit. |
|
1978 setAutofilled(false); |
|
1979 } |
|
1980 |
|
1981 void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths) |
|
1982 { |
|
1983 m_fileList->clear(); |
|
1984 int size = paths.size(); |
|
1985 |
|
1986 #if ENABLE(DIRECTORY_UPLOAD) |
|
1987 // If a directory is being selected, the UI allows a directory to be chosen |
|
1988 // and the paths provided here share a root directory somewhere up the tree; |
|
1989 // we want to store only the relative paths from that point. |
|
1990 if (webkitdirectory() && size > 0) { |
|
1991 String rootPath = directoryName(paths[0]); |
|
1992 // Find the common root path. |
|
1993 for (int i = 1; i < size; i++) { |
|
1994 while (!paths[i].startsWith(rootPath)) |
|
1995 rootPath = directoryName(rootPath); |
|
1996 } |
|
1997 rootPath = directoryName(rootPath); |
|
1998 ASSERT(rootPath.length()); |
|
1999 for (int i = 0; i < size; i++) |
|
2000 m_fileList->append(File::create(paths[i].substring(1 + rootPath.length()), paths[i])); |
|
2001 } else { |
|
2002 for (int i = 0; i < size; i++) |
|
2003 m_fileList->append(File::create(paths[i])); |
|
2004 } |
|
2005 #else |
|
2006 for (int i = 0; i < size; i++) |
|
2007 m_fileList->append(File::create(paths[i])); |
|
2008 #endif |
|
2009 |
|
2010 setFormControlValueMatchesRenderer(true); |
|
2011 InputElement::notifyFormStateChanged(this); |
|
2012 setNeedsValidityCheck(); |
|
2013 } |
|
2014 |
|
2015 bool HTMLInputElement::storesValueSeparateFromAttribute() const |
|
2016 { |
|
2017 switch (inputType()) { |
|
2018 case BUTTON: |
|
2019 case CHECKBOX: |
|
2020 case HIDDEN: |
|
2021 case IMAGE: |
|
2022 case RADIO: |
|
2023 case RESET: |
|
2024 case SUBMIT: |
|
2025 return false; |
|
2026 case COLOR: |
|
2027 case DATE: |
|
2028 case DATETIME: |
|
2029 case DATETIMELOCAL: |
|
2030 case EMAIL: |
|
2031 case FILE: |
|
2032 case ISINDEX: |
|
2033 case MONTH: |
|
2034 case NUMBER: |
|
2035 case PASSWORD: |
|
2036 case RANGE: |
|
2037 case SEARCH: |
|
2038 case TELEPHONE: |
|
2039 case TEXT: |
|
2040 case TIME: |
|
2041 case URL: |
|
2042 case WEEK: |
|
2043 return true; |
|
2044 } |
|
2045 return false; |
|
2046 } |
|
2047 |
|
2048 struct EventHandlingState { |
|
2049 RefPtr<HTMLInputElement> m_currRadio; |
|
2050 bool m_indeterminate; |
|
2051 bool m_checked; |
|
2052 |
|
2053 EventHandlingState(bool indeterminate, bool checked) |
|
2054 : m_indeterminate(indeterminate) |
|
2055 , m_checked(checked) { } |
|
2056 }; |
|
2057 |
|
2058 void* HTMLInputElement::preDispatchEventHandler(Event *evt) |
|
2059 { |
|
2060 // preventDefault or "return false" are used to reverse the automatic checking/selection we do here. |
|
2061 // This result gives us enough info to perform the "undo" in postDispatch of the action we take here. |
|
2062 void* result = 0; |
|
2063 if ((inputType() == CHECKBOX || inputType() == RADIO) && evt->isMouseEvent() |
|
2064 && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { |
|
2065 |
|
2066 EventHandlingState* state = new EventHandlingState(indeterminate(), checked()); |
|
2067 |
|
2068 if (inputType() == CHECKBOX) { |
|
2069 if (indeterminate()) |
|
2070 setIndeterminate(false); |
|
2071 else |
|
2072 setChecked(!checked(), true); |
|
2073 } else { |
|
2074 // For radio buttons, store the current selected radio object. |
|
2075 // We really want radio groups to end up in sane states, i.e., to have something checked. |
|
2076 // Therefore if nothing is currently selected, we won't allow this action to be "undone", since |
|
2077 // we want some object in the radio group to actually get selected. |
|
2078 HTMLInputElement* currRadio = checkedRadioButtons(this).checkedButtonForGroup(name()); |
|
2079 if (currRadio) { |
|
2080 // We have a radio button selected that is not us. Cache it in our result field and ref it so |
|
2081 // that it can't be destroyed. |
|
2082 state->m_currRadio = currRadio; |
|
2083 } |
|
2084 if (indeterminate()) |
|
2085 setIndeterminate(false); |
|
2086 setChecked(true, true); |
|
2087 } |
|
2088 result = state; |
|
2089 } |
|
2090 return result; |
|
2091 } |
|
2092 |
|
2093 void HTMLInputElement::postDispatchEventHandler(Event *evt, void* data) |
|
2094 { |
|
2095 if ((inputType() == CHECKBOX || inputType() == RADIO) && evt->isMouseEvent() |
|
2096 && evt->type() == eventNames().clickEvent && static_cast<MouseEvent*>(evt)->button() == LeftButton) { |
|
2097 |
|
2098 if (EventHandlingState* state = reinterpret_cast<EventHandlingState*>(data)) { |
|
2099 if (inputType() == CHECKBOX) { |
|
2100 // Reverse the checking we did in preDispatch. |
|
2101 if (evt->defaultPrevented() || evt->defaultHandled()) { |
|
2102 setIndeterminate(state->m_indeterminate); |
|
2103 setChecked(state->m_checked); |
|
2104 } |
|
2105 } else { |
|
2106 HTMLInputElement* input = state->m_currRadio.get(); |
|
2107 if (evt->defaultPrevented() || evt->defaultHandled()) { |
|
2108 // Restore the original selected radio button if possible. |
|
2109 // Make sure it is still a radio button and only do the restoration if it still |
|
2110 // belongs to our group. |
|
2111 |
|
2112 if (input && input->form() == form() && input->inputType() == RADIO && input->name() == name()) { |
|
2113 // Ok, the old radio button is still in our form and in our group and is still a |
|
2114 // radio button, so it's safe to restore selection to it. |
|
2115 input->setChecked(true); |
|
2116 } |
|
2117 setIndeterminate(state->m_indeterminate); |
|
2118 } |
|
2119 } |
|
2120 delete state; |
|
2121 } |
|
2122 |
|
2123 // Left clicks on radio buttons and check boxes already performed default actions in preDispatchEventHandler(). |
|
2124 evt->setDefaultHandled(); |
|
2125 } |
|
2126 } |
|
2127 |
|
2128 void HTMLInputElement::defaultEventHandler(Event* evt) |
|
2129 { |
|
2130 // FIXME: It would be better to refactor this for the different types of input element. |
|
2131 // Having them all in one giant function makes this hard to read, and almost all the handling is type-specific. |
|
2132 |
|
2133 bool implicitSubmission = false; |
|
2134 |
|
2135 if (isTextField() && evt->type() == eventNames().textInputEvent && evt->isTextEvent() && static_cast<TextEvent*>(evt)->data() == "\n") |
|
2136 implicitSubmission = true; |
|
2137 |
|
2138 if (inputType() == IMAGE && evt->isMouseEvent() && evt->type() == eventNames().clickEvent) { |
|
2139 // record the mouse position for when we get the DOMActivate event |
|
2140 MouseEvent* me = static_cast<MouseEvent*>(evt); |
|
2141 // FIXME: We could just call offsetX() and offsetY() on the event, |
|
2142 // but that's currently broken, so for now do the computation here. |
|
2143 if (me->isSimulated() || !renderer()) { |
|
2144 m_xPos = 0; |
|
2145 m_yPos = 0; |
|
2146 } else { |
|
2147 // FIXME: This doesn't work correctly with transforms. |
|
2148 // FIXME: pageX/pageY need adjusting for pageZoomFactor(). Use actualPageLocation()? |
|
2149 IntPoint absOffset = roundedIntPoint(renderer()->localToAbsolute()); |
|
2150 m_xPos = me->pageX() - absOffset.x(); |
|
2151 m_yPos = me->pageY() - absOffset.y(); |
|
2152 } |
|
2153 } |
|
2154 |
|
2155 if (hasSpinButton() && evt->type() == eventNames().keydownEvent && evt->isKeyboardEvent()) { |
|
2156 String key = static_cast<KeyboardEvent*>(evt)->keyIdentifier(); |
|
2157 int step = 0; |
|
2158 if (key == "Up") |
|
2159 step = 1; |
|
2160 else if (key == "Down") |
|
2161 step = -1; |
|
2162 if (step) { |
|
2163 stepUpFromRenderer(step); |
|
2164 evt->setDefaultHandled(); |
|
2165 return; |
|
2166 } |
|
2167 } |
|
2168 |
|
2169 if (isTextField() |
|
2170 && evt->type() == eventNames().keydownEvent |
|
2171 && evt->isKeyboardEvent() |
|
2172 && focused() |
|
2173 && document()->frame() |
|
2174 && document()->frame()->doTextFieldCommandFromEvent(this, static_cast<KeyboardEvent*>(evt))) { |
|
2175 evt->setDefaultHandled(); |
|
2176 return; |
|
2177 } |
|
2178 |
|
2179 if (inputType() == RADIO |
|
2180 && evt->isMouseEvent() |
|
2181 && evt->type() == eventNames().clickEvent |
|
2182 && static_cast<MouseEvent*>(evt)->button() == LeftButton) { |
|
2183 evt->setDefaultHandled(); |
|
2184 return; |
|
2185 } |
|
2186 |
|
2187 // Call the base event handler before any of our own event handling for almost all events in text fields. |
|
2188 // Makes editing keyboard handling take precedence over the keydown and keypress handling in this function. |
|
2189 bool callBaseClassEarly = isTextField() && !implicitSubmission |
|
2190 && (evt->type() == eventNames().keydownEvent || evt->type() == eventNames().keypressEvent); |
|
2191 if (callBaseClassEarly) { |
|
2192 HTMLFormControlElementWithState::defaultEventHandler(evt); |
|
2193 if (evt->defaultHandled()) |
|
2194 return; |
|
2195 } |
|
2196 |
|
2197 // DOMActivate events cause the input to be "activated" - in the case of image and submit inputs, this means |
|
2198 // actually submitting the form. For reset inputs, the form is reset. These events are sent when the user clicks |
|
2199 // on the element, or presses enter while it is the active element. JavaScript code wishing to activate the element |
|
2200 // must dispatch a DOMActivate event - a click event will not do the job. |
|
2201 if (evt->type() == eventNames().DOMActivateEvent && !disabled()) { |
|
2202 if (inputType() == IMAGE || inputType() == SUBMIT || inputType() == RESET) { |
|
2203 if (!form()) |
|
2204 return; |
|
2205 if (inputType() == RESET) |
|
2206 form()->reset(); |
|
2207 else { |
|
2208 m_activeSubmit = true; |
|
2209 // FIXME: Would be cleaner to get m_xPos and m_yPos out of the underlying mouse |
|
2210 // event (if any) here instead of relying on the variables set above when |
|
2211 // processing the click event. Even better, appendFormData could pass the |
|
2212 // event in, and then we could get rid of m_xPos and m_yPos altogether! |
|
2213 if (!form()->prepareSubmit(evt)) { |
|
2214 m_xPos = 0; |
|
2215 m_yPos = 0; |
|
2216 } |
|
2217 m_activeSubmit = false; |
|
2218 } |
|
2219 } else if (inputType() == FILE && renderer()) |
|
2220 toRenderFileUploadControl(renderer())->click(); |
|
2221 } |
|
2222 |
|
2223 // Use key press event here since sending simulated mouse events |
|
2224 // on key down blocks the proper sending of the key press event. |
|
2225 if (evt->type() == eventNames().keypressEvent && evt->isKeyboardEvent()) { |
|
2226 bool clickElement = false; |
|
2227 |
|
2228 int charCode = static_cast<KeyboardEvent*>(evt)->charCode(); |
|
2229 |
|
2230 if (charCode == '\r') { |
|
2231 switch (inputType()) { |
|
2232 case CHECKBOX: |
|
2233 case COLOR: |
|
2234 case DATE: |
|
2235 case DATETIME: |
|
2236 case DATETIMELOCAL: |
|
2237 case EMAIL: |
|
2238 case HIDDEN: |
|
2239 case ISINDEX: |
|
2240 case MONTH: |
|
2241 case NUMBER: |
|
2242 case PASSWORD: |
|
2243 case RADIO: |
|
2244 case RANGE: |
|
2245 case SEARCH: |
|
2246 case TELEPHONE: |
|
2247 case TEXT: |
|
2248 case TIME: |
|
2249 case URL: |
|
2250 case WEEK: |
|
2251 // Simulate mouse click on the default form button for enter for these types of elements. |
|
2252 implicitSubmission = true; |
|
2253 break; |
|
2254 case BUTTON: |
|
2255 case FILE: |
|
2256 case IMAGE: |
|
2257 case RESET: |
|
2258 case SUBMIT: |
|
2259 // Simulate mouse click for enter for these types of elements. |
|
2260 clickElement = true; |
|
2261 break; |
|
2262 } |
|
2263 } else if (charCode == ' ') { |
|
2264 switch (inputType()) { |
|
2265 case BUTTON: |
|
2266 case CHECKBOX: |
|
2267 case FILE: |
|
2268 case IMAGE: |
|
2269 case RESET: |
|
2270 case SUBMIT: |
|
2271 case RADIO: |
|
2272 // Prevent scrolling down the page. |
|
2273 evt->setDefaultHandled(); |
|
2274 return; |
|
2275 default: |
|
2276 break; |
|
2277 } |
|
2278 } |
|
2279 |
|
2280 if (clickElement) { |
|
2281 dispatchSimulatedClick(evt); |
|
2282 evt->setDefaultHandled(); |
|
2283 return; |
|
2284 } |
|
2285 } |
|
2286 |
|
2287 if (evt->type() == eventNames().keydownEvent && evt->isKeyboardEvent()) { |
|
2288 String key = static_cast<KeyboardEvent*>(evt)->keyIdentifier(); |
|
2289 |
|
2290 if (key == "U+0020") { |
|
2291 switch (inputType()) { |
|
2292 case BUTTON: |
|
2293 case CHECKBOX: |
|
2294 case FILE: |
|
2295 case IMAGE: |
|
2296 case RESET: |
|
2297 case SUBMIT: |
|
2298 case RADIO: |
|
2299 setActive(true, true); |
|
2300 // No setDefaultHandled(), because IE dispatches a keypress in this case |
|
2301 // and the caller will only dispatch a keypress if we don't call setDefaultHandled. |
|
2302 return; |
|
2303 default: |
|
2304 break; |
|
2305 } |
|
2306 } |
|
2307 |
|
2308 if (inputType() == RADIO && (key == "Up" || key == "Down" || key == "Left" || key == "Right")) { |
|
2309 // Left and up mean "previous radio button". |
|
2310 // Right and down mean "next radio button". |
|
2311 // Tested in WinIE, and even for RTL, left still means previous radio button (and so moves |
|
2312 // to the right). Seems strange, but we'll match it. |
|
2313 bool forward = (key == "Down" || key == "Right"); |
|
2314 |
|
2315 // We can only stay within the form's children if the form hasn't been demoted to a leaf because |
|
2316 // of malformed HTML. |
|
2317 Node* n = this; |
|
2318 while ((n = (forward ? n->traverseNextNode() : n->traversePreviousNode()))) { |
|
2319 // Once we encounter a form element, we know we're through. |
|
2320 if (n->hasTagName(formTag)) |
|
2321 break; |
|
2322 |
|
2323 // Look for more radio buttons. |
|
2324 if (n->hasTagName(inputTag)) { |
|
2325 HTMLInputElement* elt = static_cast<HTMLInputElement*>(n); |
|
2326 if (elt->form() != form()) |
|
2327 break; |
|
2328 if (n->hasTagName(inputTag)) { |
|
2329 HTMLInputElement* inputElt = static_cast<HTMLInputElement*>(n); |
|
2330 if (inputElt->inputType() == RADIO && inputElt->name() == name() && inputElt->isFocusable()) { |
|
2331 inputElt->setChecked(true); |
|
2332 document()->setFocusedNode(inputElt); |
|
2333 inputElt->dispatchSimulatedClick(evt, false, false); |
|
2334 evt->setDefaultHandled(); |
|
2335 break; |
|
2336 } |
|
2337 } |
|
2338 } |
|
2339 } |
|
2340 } |
|
2341 } |
|
2342 |
|
2343 if (evt->type() == eventNames().keyupEvent && evt->isKeyboardEvent()) { |
|
2344 bool clickElement = false; |
|
2345 |
|
2346 String key = static_cast<KeyboardEvent*>(evt)->keyIdentifier(); |
|
2347 |
|
2348 if (key == "U+0020") { |
|
2349 switch (inputType()) { |
|
2350 case BUTTON: |
|
2351 case CHECKBOX: |
|
2352 case FILE: |
|
2353 case IMAGE: |
|
2354 case RESET: |
|
2355 case SUBMIT: |
|
2356 // Simulate mouse click for spacebar for these types of elements. |
|
2357 // The AppKit already does this for some, but not all, of them. |
|
2358 clickElement = true; |
|
2359 break; |
|
2360 case RADIO: |
|
2361 // If an unselected radio is tabbed into (because the entire group has nothing |
|
2362 // checked, or because of some explicit .focus() call), then allow space to check it. |
|
2363 if (!checked()) |
|
2364 clickElement = true; |
|
2365 break; |
|
2366 case COLOR: |
|
2367 case DATE: |
|
2368 case DATETIME: |
|
2369 case DATETIMELOCAL: |
|
2370 case EMAIL: |
|
2371 case HIDDEN: |
|
2372 case ISINDEX: |
|
2373 case MONTH: |
|
2374 case NUMBER: |
|
2375 case PASSWORD: |
|
2376 case RANGE: |
|
2377 case SEARCH: |
|
2378 case TELEPHONE: |
|
2379 case TEXT: |
|
2380 case TIME: |
|
2381 case URL: |
|
2382 case WEEK: |
|
2383 break; |
|
2384 } |
|
2385 } |
|
2386 |
|
2387 if (clickElement) { |
|
2388 if (active()) |
|
2389 dispatchSimulatedClick(evt); |
|
2390 evt->setDefaultHandled(); |
|
2391 return; |
|
2392 } |
|
2393 } |
|
2394 |
|
2395 if (implicitSubmission) { |
|
2396 if (isSearchField()) { |
|
2397 addSearchResult(); |
|
2398 onSearch(); |
|
2399 } |
|
2400 // Fire onChange for text fields. |
|
2401 RenderObject* r = renderer(); |
|
2402 if (r && r->isTextField() && toRenderTextControl(r)->wasChangedSinceLastChangeEvent()) { |
|
2403 dispatchFormControlChangeEvent(); |
|
2404 // Refetch the renderer since arbitrary JS code run during onchange can do anything, including destroying it. |
|
2405 r = renderer(); |
|
2406 if (r && r->isTextField()) |
|
2407 toRenderTextControl(r)->setChangedSinceLastChangeEvent(false); |
|
2408 } |
|
2409 |
|
2410 RefPtr<HTMLFormElement> formForSubmission = form(); |
|
2411 // If there is no form and the element is an <isindex>, then create a temporary form just to be used for submission. |
|
2412 if (!formForSubmission && inputType() == ISINDEX) |
|
2413 formForSubmission = createTemporaryFormForIsIndex(); |
|
2414 |
|
2415 // Form may never have been present, or may have been destroyed by code responding to the change event. |
|
2416 if (formForSubmission) |
|
2417 formForSubmission->submitImplicitly(evt, canTriggerImplicitSubmission()); |
|
2418 |
|
2419 evt->setDefaultHandled(); |
|
2420 return; |
|
2421 } |
|
2422 |
|
2423 if (evt->isBeforeTextInsertedEvent()) |
|
2424 handleBeforeTextInsertedEvent(evt); |
|
2425 |
|
2426 if (isTextField() && renderer() && (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent() || evt->type() == eventNames().blurEvent || evt->type() == eventNames().focusEvent)) |
|
2427 toRenderTextControlSingleLine(renderer())->forwardEvent(evt); |
|
2428 |
|
2429 if (inputType() == RANGE && renderer() && (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent())) |
|
2430 toRenderSlider(renderer())->forwardEvent(evt); |
|
2431 |
|
2432 if (!callBaseClassEarly && !evt->defaultHandled()) |
|
2433 HTMLFormControlElementWithState::defaultEventHandler(evt); |
|
2434 } |
|
2435 |
|
2436 void HTMLInputElement::handleBeforeTextInsertedEvent(Event* event) |
|
2437 { |
|
2438 if (inputType() == NUMBER) { |
|
2439 BeforeTextInsertedEvent* textEvent = static_cast<BeforeTextInsertedEvent*>(event); |
|
2440 unsigned length = textEvent->text().length(); |
|
2441 bool hasInvalidChar = false; |
|
2442 for (unsigned i = 0; i < length; ++i) { |
|
2443 if (!isNumberCharacter(textEvent->text()[i])) { |
|
2444 hasInvalidChar = true; |
|
2445 break; |
|
2446 } |
|
2447 } |
|
2448 if (hasInvalidChar) { |
|
2449 Vector<UChar> stripped; |
|
2450 stripped.reserveCapacity(length); |
|
2451 for (unsigned i = 0; i < length; ++i) { |
|
2452 UChar ch = textEvent->text()[i]; |
|
2453 if (!isNumberCharacter(ch)) |
|
2454 continue; |
|
2455 stripped.append(ch); |
|
2456 } |
|
2457 textEvent->setText(String::adopt(stripped)); |
|
2458 } |
|
2459 } |
|
2460 InputElement::handleBeforeTextInsertedEvent(m_data, this, this, event); |
|
2461 } |
|
2462 |
|
2463 PassRefPtr<HTMLFormElement> HTMLInputElement::createTemporaryFormForIsIndex() |
|
2464 { |
|
2465 RefPtr<HTMLFormElement> form = HTMLFormElement::create(document()); |
|
2466 form->registerFormElement(this); |
|
2467 form->setMethod("GET"); |
|
2468 if (!document()->baseURL().isEmpty()) { |
|
2469 // We treat the href property of the <base> element as the form action, as per section 7.5 |
|
2470 // "Queries and Indexes" of the HTML 2.0 spec. <http://www.w3.org/MarkUp/html-spec/html-spec_7.html#SEC7.5>. |
|
2471 form->setAction(document()->baseURL().string()); |
|
2472 } |
|
2473 return form.release(); |
|
2474 } |
|
2475 |
|
2476 bool HTMLInputElement::isURLAttribute(Attribute *attr) const |
|
2477 { |
|
2478 return (attr->name() == srcAttr); |
|
2479 } |
|
2480 |
|
2481 String HTMLInputElement::defaultValue() const |
|
2482 { |
|
2483 return getAttribute(valueAttr); |
|
2484 } |
|
2485 |
|
2486 void HTMLInputElement::setDefaultValue(const String &value) |
|
2487 { |
|
2488 setAttribute(valueAttr, value); |
|
2489 } |
|
2490 |
|
2491 bool HTMLInputElement::defaultChecked() const |
|
2492 { |
|
2493 return !getAttribute(checkedAttr).isNull(); |
|
2494 } |
|
2495 |
|
2496 void HTMLInputElement::setDefaultName(const AtomicString& name) |
|
2497 { |
|
2498 m_data.setName(name); |
|
2499 } |
|
2500 |
|
2501 String HTMLInputElement::accept() const |
|
2502 { |
|
2503 return getAttribute(acceptAttr); |
|
2504 } |
|
2505 |
|
2506 String HTMLInputElement::alt() const |
|
2507 { |
|
2508 return getAttribute(altAttr); |
|
2509 } |
|
2510 |
|
2511 int HTMLInputElement::maxLength() const |
|
2512 { |
|
2513 return m_data.maxLength(); |
|
2514 } |
|
2515 |
|
2516 void HTMLInputElement::setMaxLength(int maxLength, ExceptionCode& ec) |
|
2517 { |
|
2518 if (maxLength < 0) |
|
2519 ec = INDEX_SIZE_ERR; |
|
2520 else |
|
2521 setAttribute(maxlengthAttr, String::number(maxLength)); |
|
2522 } |
|
2523 |
|
2524 bool HTMLInputElement::multiple() const |
|
2525 { |
|
2526 return !getAttribute(multipleAttr).isNull(); |
|
2527 } |
|
2528 |
|
2529 #if ENABLE(DIRECTORY_UPLOAD) |
|
2530 bool HTMLInputElement::webkitdirectory() const |
|
2531 { |
|
2532 return !getAttribute(webkitdirectoryAttr).isNull(); |
|
2533 } |
|
2534 #endif |
|
2535 |
|
2536 void HTMLInputElement::setSize(unsigned size) |
|
2537 { |
|
2538 setAttribute(sizeAttr, String::number(size)); |
|
2539 } |
|
2540 |
|
2541 KURL HTMLInputElement::src() const |
|
2542 { |
|
2543 return document()->completeURL(getAttribute(srcAttr)); |
|
2544 } |
|
2545 |
|
2546 void HTMLInputElement::setAutofilled(bool b) |
|
2547 { |
|
2548 if (b == m_autofilled) |
|
2549 return; |
|
2550 |
|
2551 m_autofilled = b; |
|
2552 setNeedsStyleRecalc(); |
|
2553 } |
|
2554 |
|
2555 FileList* HTMLInputElement::files() |
|
2556 { |
|
2557 if (inputType() != FILE) |
|
2558 return 0; |
|
2559 return m_fileList.get(); |
|
2560 } |
|
2561 |
|
2562 String HTMLInputElement::sanitizeValue(const String& proposedValue) const |
|
2563 { |
|
2564 if (isTextField()) |
|
2565 return InputElement::sanitizeValueForTextField(this, proposedValue); |
|
2566 |
|
2567 // If the proposedValue is null than this is a reset scenario and we |
|
2568 // want the range input's value attribute to take priority over the |
|
2569 // calculated default (middle) value. |
|
2570 if (inputType() == RANGE && !proposedValue.isNull()) |
|
2571 return serializeForNumberType(StepRange(this).clampValue(proposedValue)); |
|
2572 |
|
2573 return proposedValue; |
|
2574 } |
|
2575 |
|
2576 bool HTMLInputElement::needsActivationCallback() |
|
2577 { |
|
2578 return inputType() == PASSWORD || m_autocomplete == Off; |
|
2579 } |
|
2580 |
|
2581 void HTMLInputElement::registerForActivationCallbackIfNeeded() |
|
2582 { |
|
2583 if (needsActivationCallback()) |
|
2584 document()->registerForDocumentActivationCallbacks(this); |
|
2585 } |
|
2586 |
|
2587 void HTMLInputElement::unregisterForActivationCallbackIfNeeded() |
|
2588 { |
|
2589 if (!needsActivationCallback()) |
|
2590 document()->unregisterForDocumentActivationCallbacks(this); |
|
2591 } |
|
2592 |
|
2593 bool HTMLInputElement::isRequiredFormControl() const |
|
2594 { |
|
2595 if (!required()) |
|
2596 return false; |
|
2597 |
|
2598 switch (inputType()) { |
|
2599 case CHECKBOX: |
|
2600 case DATE: |
|
2601 case DATETIME: |
|
2602 case DATETIMELOCAL: |
|
2603 case EMAIL: |
|
2604 case FILE: |
|
2605 case MONTH: |
|
2606 case NUMBER: |
|
2607 case PASSWORD: |
|
2608 case RADIO: |
|
2609 case SEARCH: |
|
2610 case TELEPHONE: |
|
2611 case TEXT: |
|
2612 case TIME: |
|
2613 case URL: |
|
2614 case WEEK: |
|
2615 return true; |
|
2616 case BUTTON: |
|
2617 case COLOR: |
|
2618 case HIDDEN: |
|
2619 case IMAGE: |
|
2620 case ISINDEX: |
|
2621 case RANGE: |
|
2622 case RESET: |
|
2623 case SUBMIT: |
|
2624 return false; |
|
2625 } |
|
2626 |
|
2627 ASSERT_NOT_REACHED(); |
|
2628 return false; |
|
2629 } |
|
2630 |
|
2631 void HTMLInputElement::cacheSelection(int start, int end) |
|
2632 { |
|
2633 m_data.setCachedSelectionStart(start); |
|
2634 m_data.setCachedSelectionEnd(end); |
|
2635 } |
|
2636 |
|
2637 void HTMLInputElement::addSearchResult() |
|
2638 { |
|
2639 ASSERT(isSearchField()); |
|
2640 if (renderer()) |
|
2641 toRenderTextControlSingleLine(renderer())->addSearchResult(); |
|
2642 } |
|
2643 |
|
2644 void HTMLInputElement::onSearch() |
|
2645 { |
|
2646 ASSERT(isSearchField()); |
|
2647 if (renderer()) |
|
2648 toRenderTextControlSingleLine(renderer())->stopSearchEventTimer(); |
|
2649 dispatchEvent(Event::create(eventNames().searchEvent, true, false)); |
|
2650 } |
|
2651 |
|
2652 void HTMLInputElement::documentDidBecomeActive() |
|
2653 { |
|
2654 ASSERT(needsActivationCallback()); |
|
2655 reset(); |
|
2656 } |
|
2657 |
|
2658 void HTMLInputElement::willMoveToNewOwnerDocument() |
|
2659 { |
|
2660 if (m_imageLoader) |
|
2661 m_imageLoader->elementWillMoveToNewOwnerDocument(); |
|
2662 |
|
2663 // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered |
|
2664 if (needsActivationCallback()) |
|
2665 document()->unregisterForDocumentActivationCallbacks(this); |
|
2666 |
|
2667 document()->checkedRadioButtons().removeButton(this); |
|
2668 |
|
2669 HTMLFormControlElementWithState::willMoveToNewOwnerDocument(); |
|
2670 } |
|
2671 |
|
2672 void HTMLInputElement::didMoveToNewOwnerDocument() |
|
2673 { |
|
2674 registerForActivationCallbackIfNeeded(); |
|
2675 |
|
2676 HTMLFormControlElementWithState::didMoveToNewOwnerDocument(); |
|
2677 } |
|
2678 |
|
2679 void HTMLInputElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const |
|
2680 { |
|
2681 HTMLFormControlElementWithState::addSubresourceAttributeURLs(urls); |
|
2682 |
|
2683 addSubresourceURL(urls, src()); |
|
2684 } |
|
2685 |
|
2686 bool HTMLInputElement::recalcWillValidate() const |
|
2687 { |
|
2688 switch (inputType()) { |
|
2689 case CHECKBOX: |
|
2690 case COLOR: |
|
2691 case DATE: |
|
2692 case DATETIME: |
|
2693 case DATETIMELOCAL: |
|
2694 case EMAIL: |
|
2695 case FILE: |
|
2696 case ISINDEX: |
|
2697 case MONTH: |
|
2698 case NUMBER: |
|
2699 case PASSWORD: |
|
2700 case RADIO: |
|
2701 case RANGE: |
|
2702 case SEARCH: |
|
2703 case TELEPHONE: |
|
2704 case TEXT: |
|
2705 case TIME: |
|
2706 case URL: |
|
2707 case WEEK: |
|
2708 return HTMLFormControlElementWithState::recalcWillValidate(); |
|
2709 case BUTTON: |
|
2710 case HIDDEN: |
|
2711 case IMAGE: |
|
2712 case RESET: |
|
2713 case SUBMIT: |
|
2714 return false; |
|
2715 } |
|
2716 ASSERT_NOT_REACHED(); |
|
2717 return false; |
|
2718 } |
|
2719 |
|
2720 bool HTMLInputElement::parseToDateComponents(InputType type, const String& formString, DateComponents* out) |
|
2721 { |
|
2722 if (formString.isEmpty()) |
|
2723 return false; |
|
2724 DateComponents ignoredResult; |
|
2725 if (!out) |
|
2726 out = &ignoredResult; |
|
2727 const UChar* characters = formString.characters(); |
|
2728 unsigned length = formString.length(); |
|
2729 unsigned end; |
|
2730 |
|
2731 switch (type) { |
|
2732 case DATE: |
|
2733 return out->parseDate(characters, length, 0, end) && end == length; |
|
2734 case DATETIME: |
|
2735 return out->parseDateTime(characters, length, 0, end) && end == length; |
|
2736 case DATETIMELOCAL: |
|
2737 return out->parseDateTimeLocal(characters, length, 0, end) && end == length; |
|
2738 case MONTH: |
|
2739 return out->parseMonth(characters, length, 0, end) && end == length; |
|
2740 case WEEK: |
|
2741 return out->parseWeek(characters, length, 0, end) && end == length; |
|
2742 case TIME: |
|
2743 return out->parseTime(characters, length, 0, end) && end == length; |
|
2744 default: |
|
2745 ASSERT_NOT_REACHED(); |
|
2746 return false; |
|
2747 } |
|
2748 } |
|
2749 |
|
2750 #if ENABLE(DATALIST) |
|
2751 |
|
2752 HTMLElement* HTMLInputElement::list() const |
|
2753 { |
|
2754 return dataList(); |
|
2755 } |
|
2756 |
|
2757 HTMLDataListElement* HTMLInputElement::dataList() const |
|
2758 { |
|
2759 if (!m_hasNonEmptyList) |
|
2760 return 0; |
|
2761 |
|
2762 switch (inputType()) { |
|
2763 case COLOR: |
|
2764 case DATE: |
|
2765 case DATETIME: |
|
2766 case DATETIMELOCAL: |
|
2767 case EMAIL: |
|
2768 case MONTH: |
|
2769 case NUMBER: |
|
2770 case RANGE: |
|
2771 case SEARCH: |
|
2772 case TELEPHONE: |
|
2773 case TEXT: |
|
2774 case TIME: |
|
2775 case URL: |
|
2776 case WEEK: { |
|
2777 Element* element = document()->getElementById(getAttribute(listAttr)); |
|
2778 if (element && element->hasTagName(datalistTag)) |
|
2779 return static_cast<HTMLDataListElement*>(element); |
|
2780 break; |
|
2781 } |
|
2782 case BUTTON: |
|
2783 case CHECKBOX: |
|
2784 case FILE: |
|
2785 case HIDDEN: |
|
2786 case IMAGE: |
|
2787 case ISINDEX: |
|
2788 case PASSWORD: |
|
2789 case RADIO: |
|
2790 case RESET: |
|
2791 case SUBMIT: |
|
2792 break; |
|
2793 } |
|
2794 return 0; |
|
2795 } |
|
2796 |
|
2797 HTMLOptionElement* HTMLInputElement::selectedOption() const |
|
2798 { |
|
2799 String currentValue = value(); |
|
2800 // The empty value never matches to a datalist option because it |
|
2801 // doesn't represent a suggestion according to the standard. |
|
2802 if (currentValue.isEmpty()) |
|
2803 return 0; |
|
2804 |
|
2805 HTMLDataListElement* sourceElement = dataList(); |
|
2806 if (!sourceElement) |
|
2807 return 0; |
|
2808 RefPtr<HTMLCollection> options = sourceElement->options(); |
|
2809 for (unsigned i = 0; options && i < options->length(); ++i) { |
|
2810 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(options->item(i)); |
|
2811 if (!option->disabled() && currentValue == option->value()) |
|
2812 return option; |
|
2813 } |
|
2814 return 0; |
|
2815 } |
|
2816 |
|
2817 #endif // ENABLE(DATALIST) |
|
2818 |
|
2819 void HTMLInputElement::stepUpFromRenderer(int n) |
|
2820 { |
|
2821 // The difference from stepUp()/stepDown() is: |
|
2822 // If the current value is invalid, the value will be |
|
2823 // - the minimum value if n > 0 |
|
2824 // - the maximum value if n < 0 |
|
2825 |
|
2826 ASSERT(hasSpinButton()); |
|
2827 if (!hasSpinButton()) |
|
2828 return; |
|
2829 ASSERT(n); |
|
2830 if (!n) |
|
2831 return; |
|
2832 |
|
2833 const double nan = numeric_limits<double>::quiet_NaN(); |
|
2834 double current = parseToDouble(value(), nan); |
|
2835 if (!isfinite(current)) { |
|
2836 setValue(serialize(n > 0 ? minimum() : maximum())); |
|
2837 return; |
|
2838 } |
|
2839 ExceptionCode ec; |
|
2840 stepUp(n, ec); |
|
2841 } |
|
2842 |
|
2843 #if ENABLE(WCSS) |
|
2844 void HTMLInputElement::setWapInputFormat(String& mask) |
|
2845 { |
|
2846 String validateMask = validateInputMask(m_data, mask); |
|
2847 if (!validateMask.isEmpty()) |
|
2848 m_data.setInputFormatMask(validateMask); |
|
2849 } |
|
2850 #endif |
|
2851 |
|
2852 #if ENABLE(INPUT_SPEECH) |
|
2853 bool HTMLInputElement::isSpeechEnabled() const |
|
2854 { |
|
2855 switch (inputType()) { |
|
2856 // FIXME: Add support for RANGE, EMAIL, URL, COLOR and DATE/TIME input types. |
|
2857 case NUMBER: |
|
2858 case PASSWORD: |
|
2859 case SEARCH: |
|
2860 case TELEPHONE: |
|
2861 case TEXT: |
|
2862 return hasAttribute(speechAttr); |
|
2863 case BUTTON: |
|
2864 case CHECKBOX: |
|
2865 case COLOR: |
|
2866 case DATE: |
|
2867 case DATETIME: |
|
2868 case DATETIMELOCAL: |
|
2869 case EMAIL: |
|
2870 case FILE: |
|
2871 case HIDDEN: |
|
2872 case IMAGE: |
|
2873 case ISINDEX: |
|
2874 case MONTH: |
|
2875 case RADIO: |
|
2876 case RANGE: |
|
2877 case RESET: |
|
2878 case SUBMIT: |
|
2879 case TIME: |
|
2880 case URL: |
|
2881 case WEEK: |
|
2882 return false; |
|
2883 } |
|
2884 return false; |
|
2885 } |
|
2886 #endif |
|
2887 |
|
2888 } // namespace |