1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This file provides common methods that can be shared by other JavaScripts.
7 goog
.provide('__crWeb.common');
9 goog
.require('__crWeb.base');
13 * Namespace for this file. It depends on |__gCrWeb| having already been
14 * injected. String 'common' is used in |__gCrWeb['common']| as it needs to be
15 * accessed in Objective-C code.
17 __gCrWeb
['common'] = {};
19 /* Beginning of anonymous object. */
22 * JSON safe object to protect against custom implementation of Object.toJSON
26 __gCrWeb
['common'].JSONSafeObject
= function JSONSafeObject() {
30 * Protect against custom implementation of Object.toJSON in host pages.
32 __gCrWeb
['common'].JSONSafeObject
.prototype.toJSON
= null;
35 * Retain the original JSON.stringify method where possible to reduce the
36 * impact of sites overriding it
38 __gCrWeb
.common
.JSONStringify
= JSON
.stringify
;
41 * Prefix used in references to form elements that have no 'id' or 'name'
43 __gCrWeb
.common
.kNamelessFormIDPrefix
= 'gChrome~';
46 * Tests an element's visiblity. This test is expensive so should be used
48 * @param {Element} element A DOM element.
49 * @return {boolean} true if the |element| is currently part of the visible
52 __gCrWeb
.common
.isElementVisible = function(element
) {
55 while (node
&& node
!== document
) {
56 if (node
.nodeType
=== Node
.ELEMENT_NODE
) {
57 var style
= window
.getComputedStyle(/** @type {Element} */(node
));
58 if (style
.display
=== 'none' || style
.visibility
=== 'hidden') {
62 // Move up the tree and test again.
63 node
= node
.parentNode
;
65 // Test reached the top of the DOM without finding a concealed ancestor.
70 * Based on Element::isFormControlElement() (WebKit)
71 * @param {Element} element A DOM element.
72 * @return {boolean} true if the |element| is a form control element.
74 __gCrWeb
.common
.isFormControlElement = function(element
) {
75 var tagName
= element
.tagName
;
76 return (tagName
=== 'INPUT' ||
77 tagName
=== 'SELECT' ||
78 tagName
=== 'TEXTAREA');
82 * Detects focusable elements.
83 * @param {Element} element A DOM element.
84 * @return {boolean} true if the |element| is focusable.
86 __gCrWeb
.common
.isFocusable = function(element
) {
87 // When the disabled or hidden attributes are present, controls do not
89 if (element
.hasAttribute('disabled') || element
.hasAttribute('hidden'))
91 return __gCrWeb
.common
.isFormControlElement(element
);
95 * Returns an array of control elements in a form.
97 * This method is based on the logic in method
98 * void WebFormElement::getFormControlElements(
99 * WebVector<WebFormControlElement>&) const
100 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
101 * WebFormElement.cpp.
103 * @param {Element} form A form element for which the control elements are
105 * @return {Array<Element>}
107 __gCrWeb
.common
.getFormControlElements = function(form
) {
112 // Get input and select elements from form.elements.
113 // TODO(chenyu): according to
114 // http://www.w3.org/TR/2011/WD-html5-20110525/forms.html, form.elements are
115 // the "listed elements whose form owner is the form element, with the
116 // exception of input elements whose type attribute is in the Image Button
117 // state, which must, for historical reasons, be excluded from this
118 // particular collection." In WebFormElement.cpp, this method is implemented
119 // by returning elements in form's associated elements that have tag 'INPUT'
120 // or 'SELECT'. Check if input Image Buttons are excluded in that
121 // implementation. Note for Autofill, as input Image Button is not
122 // considered as autofillable elements, there is no impact on Autofill
124 var elements
= form
.elements
;
125 for (var i
= 0; i
< elements
.length
; i
++) {
126 if (__gCrWeb
.common
.isFormControlElement(elements
[i
])) {
127 results
.push(elements
[i
]);
134 * Returns true if an element can be autocompleted.
136 * This method aims to provide the same logic as method
137 * bool autoComplete() const
138 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
139 * WebFormElement.cpp.
141 * @param {Element} element An element to check if it can be autocompleted.
142 * @return {boolean} true if element can be autocompleted.
144 __gCrWeb
.common
.autoComplete = function(element
) {
148 if (__gCrWeb
.common
.getLowerCaseAttribute(
149 element
, 'autocomplete') == 'off') {
152 if (__gCrWeb
.common
.getLowerCaseAttribute(
153 element
.form
, 'autocomplete') == 'off') {
160 * Returns if an element is a text field.
161 * This returns true for all of textfield-looking types such as text,
162 * password, search, email, url, and number.
164 * This method aims to provide the same logic as method
165 * bool WebInputElement::isTextField() const
166 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
167 * WebInputElement.cpp, where this information is from
168 * bool HTMLInputElement::isTextField() const
170 * return m_inputType->isTextField();
172 * (chromium/src/third_party/WebKit/Source/WebCore/html/HTMLInputElement.cpp)
174 * The implementation here is based on the following:
176 * - Method bool InputType::isTextField() defaults to be false and it is
177 * override to return true only in HTMLInputElement's subclass
178 * TextFieldInputType (chromium/src/third_party/WebKit/Source/WebCore/html/
179 * TextFieldInputType.h).
181 * - The implementation here considers all the subclasses of
182 * TextFieldInputType: NumberInputType and BaseTextInputType, which has
183 * subclasses EmailInputType, PasswordInputType, SearchInputType,
184 * TelephoneInputType, TextInputType, URLInputType. (All these classes are
185 * defined in chromium/src/third_party/WebKit/Source/WebCore/html/)
187 * @param {Element} element An element to examine if it is a text field.
188 * @return {boolean} true if element has type=text.
190 __gCrWeb
.common
.isTextField = function(element
) {
194 if (element
.type
=== 'hidden') {
197 return element
.type
=== 'text' ||
198 element
.type
=== 'email' ||
199 element
.type
=== 'password' ||
200 element
.type
=== 'search' ||
201 element
.type
=== 'tel' ||
202 element
.type
=== 'url' ||
203 element
.type
=== 'number';
207 * Sets the checked value of an input and dispatches an change event if
208 * |shouldSendChangeEvent|.
210 * This is a simplified version of the implementation of
212 * void setChecked(bool nowChecked, TextFieldEventBehavior eventBehavior)
214 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
215 * WebInputElement.cpp, which calls
216 * void HTMLInputElement::setChecked(
217 * bool nowChecked, TextFieldEventBehavior eventBehavior)
218 * in chromium/src/third_party/WebKit/Source/core/html/HTMLInputElement.cpp.
220 * @param {boolean} nowChecked The new checked value of the input element.
221 * @param {Element} input The input element of which the value is set.
222 * @param {boolean} shouldSendChangeEvent Whether a change event should be
225 __gCrWeb
.common
.setInputElementChecked = function(
226 nowChecked
, input
, shouldSendChangeEvent
) {
227 var checkedChanged
= input
.checked
!== nowChecked
;
228 input
.checked
= nowChecked
;
229 if (checkedChanged
) {
230 __gCrWeb
.common
.createAndDispatchHTMLEvent(input
, 'change', true, false);
235 * Sets the value of an input and dispatches an change event if
236 * |shouldSendChangeEvent|.
238 * It is based on the logic in
240 * void setValue(const WebString&, bool sendChangeEvent = false)
242 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
243 * WebInputElement.cpp, which calls
244 * void setValue(const String& value, TextFieldEventBehavior eventBehavior)
245 * in chromium/src/third_party/WebKit/Source/core/html/HTMLInputElement.cpp.
247 * @param {string} value The value the input element will be set.
248 * @param {Element} input The input element of which the value is set.
249 * @param {boolean} shouldSendChangeEvent Whether a change event should be
252 __gCrWeb
.common
.setInputElementValue = function(
253 value
, input
, shouldSendChangeEvent
) {
254 // In HTMLInputElement.cpp there is a check on canSetValue(value), which
255 // returns false only for file input. As file input is not relevant for
256 // autofill and this method is only used for autofill for now, there is no
257 // such check in this implementation.
258 var sanitizedValue
= __gCrWeb
.common
.sanitizeValueForInputElement(
260 var valueChanged
= sanitizedValue
!== input
.value
;
261 input
.value
= sanitizedValue
;
263 __gCrWeb
.common
.createAndDispatchHTMLEvent(input
, 'change', true, false);
268 * Returns a sanitized value of proposedValue for a given input element type.
269 * The logic is based on
271 * String sanitizeValue(const String&) const
273 * in chromium/src/third_party/WebKit/Source/core/html/InputType.h
275 * @param {string} proposedValue The proposed value.
276 * @param {Element} element The element for which the proposedValue is to be
278 * @return {string} The sanitized value.
280 __gCrWeb
.common
.sanitizeValueForInputElement = function(
281 proposedValue
, element
) {
282 if (!proposedValue
) {
286 // Method HTMLInputElement::sanitizeValue() calls InputType::sanitizeValue()
287 // (chromium/src/third_party/WebKit/Source/core/html/InputType.cpp) for
288 // non-null proposedValue. InputType::sanitizeValue() returns the original
289 // proposedValue by default and it is overridden in classes
290 // BaseDateAndTimeInputType, ColorInputType, RangeInputType and
291 // TextFieldInputType (all are in
292 // chromium/src/third_party/WebKit/Source/core/html/). Currently only
293 // TextFieldInputType is relevant and sanitizeValue() for other types of
294 // input elements has not been implemented.
295 if (__gCrWeb
.common
.isTextField(element
)) {
296 return __gCrWeb
.common
.sanitizeValueForTextFieldInputType(
297 proposedValue
, element
);
299 return proposedValue
;
303 * Returns a sanitized value for a text field.
305 * The logic is based on |String sanitizeValue(const String&)|
306 * in chromium/src/third_party/WebKit/Source/core/html/TextFieldInputType.h
307 * Note this method is overridden in EmailInputType and NumberInputType.
309 * @param {string} proposedValue The proposed value.
310 * @param {Element} element The element for which the proposedValue is to be
312 * @return {string} The sanitized value.
314 __gCrWeb
.common
.sanitizeValueForTextFieldInputType = function(
315 proposedValue
, element
) {
316 var textFieldElementType
= element
.type
;
317 if (textFieldElementType
=== 'email') {
318 return __gCrWeb
.common
.sanitizeValueForEmailInputType(
319 proposedValue
, element
);
320 } else if (textFieldElementType
=== 'number') {
321 return __gCrWeb
.common
.sanitizeValueForNumberInputType(proposedValue
);
323 var valueWithLineBreakRemoved
= proposedValue
.replace(/(\r\n|\n|\r)/gm, '');
324 // TODO(chenyu): Should we also implement numCharactersInGraphemeClusters()
325 // in chromium/src/third_party/WebKit/Source/core/platform/text/
326 // TextBreakIterator.cpp and call it here when computing newLength?
327 // Different from the implementation in TextFieldInputType.h, where a limit
328 // on the text length is considered due to
329 // https://bugs.webkit.org/show_bug.cgi?id=14536, no such limit is
330 // considered here for now.
331 var newLength
= valueWithLineBreakRemoved
.length
;
332 // This logic is from method String limitLength() in TextFieldInputType.h
333 for (var i
= 0; i
< newLength
; ++i
) {
334 var current
= valueWithLineBreakRemoved
[i
];
335 if (current
< ' ' && current
!= '\t') {
340 return valueWithLineBreakRemoved
.substring(0, newLength
);
344 * Returns the sanitized value for an email input.
346 * The logic is based on
348 * String EmailInputType::sanitizeValue(const String& proposedValue) const
350 * in chromium/src/third_party/WebKit/Source/core/html/EmailInputType.cpp
352 * @param {string} proposedValue The proposed value.
353 * @param {Element} element The element for which the proposedValue is to be
355 * @return {string} The sanitized value.
357 __gCrWeb
.common
.sanitizeValueForEmailInputType = function(
358 proposedValue
, element
) {
359 var valueWithLineBreakRemoved
= proposedValue
.replace(/(\r\n|\n\r)/gm, '');
361 if (!element
.multiple
) {
362 return __gCrWeb
.common
.trim(proposedValue
);
364 var addresses
= valueWithLineBreakRemoved
.split(',');
365 for (var i
= 0; i
< addresses
.length
; ++i
) {
366 addresses
[i
] = __gCrWeb
.common
.trim(addresses
[i
]);
368 return addresses
.join(',');
372 * Returns the sanitized value of a proposed value for a number input.
374 * The logic is based on
376 * String NumberInputType::sanitizeValue(const String& proposedValue)
379 * in chromium/src/third_party/WebKit/Source/core/html/NumberInputType.cpp
381 * Note in this implementation method Number() is used in the place of method
382 * parseToDoubleForNumberType() called in NumberInputType.cpp.
384 * @param {string} proposedValue The proposed value.
385 * @return {string} The sanitized value.
387 __gCrWeb
.common
.sanitizeValueForNumberInputType = function(proposedValue
) {
388 var sanitizedValue
= Number(proposedValue
);
389 if (isNaN(sanitizedValue
)) {
392 return sanitizedValue
.toString();
396 * Trims any whitespace from the start and end of a string.
397 * Used in preference to String.prototype.trim as this can be overridden by
400 * @param {string} str The string to be trimmed.
401 * @return {string} The string after trimming.
403 __gCrWeb
.common
.trim = function(str
) {
404 return str
.replace(/^\s+|\s+$/g, '');
408 * Returns the name that should be used for the specified |element| when
409 * storing Autofill data. Various attributes are used to attempt to identify
410 * the element, beginning with 'name' and 'id' attributes. Providing a
411 * uniquely reversible identifier for any element is a non-trivial problem;
412 * this solution attempts to satisfy the majority of cases.
414 * It aims to provide the logic in
415 * WebString nameForAutofill() const;
416 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/public/
417 * WebFormControlElement.h
419 * @param {Element} element An element of which the name for Autofill will be
421 * @return {string} the name for Autofill.
423 __gCrWeb
.common
.nameForAutofill = function(element
) {
427 var trimmedName
= element
.name
;
429 trimmedName
= __gCrWeb
.common
.trim(trimmedName
);
430 if (trimmedName
.length
> 0) {
434 trimmedName
= element
.getAttribute('id');
436 return __gCrWeb
.common
.trim(trimmedName
);
442 * Acquires the specified DOM |attribute| from the DOM |element| and returns
443 * its lower-case value, or null if not present.
444 * @param {Element} element A DOM element.
445 * @param {string} attribute An attribute name.
446 * @return {?string} Lowercase value of DOM element or null if not present.
448 __gCrWeb
.common
.getLowerCaseAttribute = function(element
, attribute
) {
452 var value
= element
.getAttribute(attribute
);
454 return value
.toLowerCase();
460 * Converts a relative URL into an absolute URL.
461 * @param {Object} doc Document.
462 * @param {string} relativeURL Relative URL.
463 * @return {string} Absolute URL.
465 __gCrWeb
.common
.absoluteURL = function(doc
, relativeURL
) {
466 // In the case of data: URL-based pages, relativeURL === absoluteURL.
467 if (doc
.location
.protocol
=== 'data:') {
468 return doc
.location
.href
;
470 var urlNormalizer
= doc
['__gCrWebURLNormalizer'];
471 if (!urlNormalizer
) {
472 urlNormalizer
= doc
.createElement('a');
473 doc
['__gCrWebURLNormalizer'] = urlNormalizer
;
476 // Use the magical quality of the <a> element. It automatically converts
477 // relative URLs into absolute ones.
478 urlNormalizer
.href
= relativeURL
;
479 return urlNormalizer
.href
;
483 * Extracts the webpage URL from the given URL by removing the query
484 * and the reference (aka fragment) from the URL.
485 * @param {string} url Web page URL.
486 * @return {string} Web page URL with query and reference removed.
488 __gCrWeb
.common
.removeQueryAndReferenceFromURL = function(url
) {
489 var queryIndex
= url
.indexOf('?');
490 if (queryIndex
!= -1) {
491 return url
.substring(0, queryIndex
);
494 var hashIndex
= url
.indexOf('#');
495 if (hashIndex
!= -1) {
496 return url
.substring(0, hashIndex
);
502 * Returns the form's |name| attribute if non-empty; otherwise the form's |id|
503 * attribute, or the index of the form (with prefix) in document.forms.
505 * It is partially based on the logic in
506 * const string16 GetFormIdentifier(const blink::WebFormElement& form)
507 * in chromium/src/components/autofill/renderer/form_autofill_util.h.
509 * @param {Element} form An element for which the identifier is returned.
510 * @return {string} a string that represents the element's identifier.
512 __gCrWeb
.common
.getFormIdentifier = function(form
) {
515 var name
= form
.getAttribute('name');
516 if (name
&& name
.length
!= 0) {
519 name
= form
.getAttribute('id');
523 // A form name must be supplied, because the element will later need to be
524 // identified from the name. A last resort is to take the index number of
525 // the form in document.forms. ids are not supposed to begin with digits (by
526 // HTML 4 spec) so this is unlikely to match a true id.
527 for (var idx
= 0; idx
!= document
.forms
.length
; idx
++) {
528 if (document
.forms
[idx
] == form
) {
529 return __gCrWeb
.common
.kNamelessFormIDPrefix
+ idx
;
536 * Returns the form element from an ID obtained from getFormIdentifier.
538 * This works on a 'best effort' basis since DOM changes can always change the
539 * actual element that the ID refers to.
541 * @param {string} name An ID string obtained via getFormIdentifier.
542 * @return {Element} The original form element, if it can be determined.
544 __gCrWeb
.common
.getFormElementFromIdentifier = function(name
) {
545 // First attempt is from the name / id supplied.
546 var form
= document
.forms
.namedItem(name
);
548 if (form
.nodeType
!== Node
.ELEMENT_NODE
)
550 return /** @type {Element} */(form
);
552 // Second attempt is from the prefixed index position of the form in
554 if (name
.indexOf(__gCrWeb
.common
.kNamelessFormIDPrefix
) == 0) {
555 var nameAsInteger
= 0 |
556 name
.substring(__gCrWeb
.common
.kNamelessFormIDPrefix
.length
);
557 if (__gCrWeb
.common
.kNamelessFormIDPrefix
+ nameAsInteger
== name
&&
558 nameAsInteger
< document
.forms
.length
) {
559 return document
.forms
[nameAsInteger
];
566 * Creates and dispatches an HTML event.
568 * @param {Element} element The element for which an event is created.
569 * @param {string} type The type of the event.
570 * @param {boolean} bubbles A boolean indicating whether the event should
571 * bubble up through the event chain or not.
572 * @param {boolean} cancelable A boolean indicating whether the event can be
575 __gCrWeb
.common
.createAndDispatchHTMLEvent = function(
576 element
, type
, bubbles
, cancelable
) {
577 var changeEvent
= element
.ownerDocument
.createEvent('HTMLEvents');
578 changeEvent
.initEvent(type
, bubbles
, cancelable
);
580 // A timer is used to avoid reentering JavaScript evaluation.
581 window
.setTimeout(function() {
582 element
.dispatchEvent(changeEvent
);
587 * Retrieves favicon information.
589 * @return {Object} Object containing favicon data.
591 __gCrWeb
.common
.getFavicons = function() {
593 var hasFavicon
= false;
594 favicons
.toJSON
= null; // Never inherit Array.prototype.toJSON.
595 var links
= document
.getElementsByTagName('link');
596 var linkCount
= links
.length
;
597 for (var i
= 0; i
< linkCount
; ++i
) {
599 var rel
= links
[i
].rel
.toLowerCase();
600 if (rel
== 'shortcut icon' ||
602 rel
== 'apple-touch-icon' ||
603 rel
== 'apple-touch-icon-precomposed') {
605 rel
: links
[i
].rel
.toLowerCase(),
608 favicons
.push(favicon
);
609 if (rel
== 'icon' || rel
== 'shortcut icon') {
616 // If an HTTP(S)? webpage does not reference a "favicon" then search
617 // for a file named "favicon.ico" at the root of the website (legacy).
618 // http://en.wikipedia.org/wiki/Favicon
619 var location
= document
.location
;
620 if (location
.protocol
== 'http:' || location
.protocol
== 'https:') {
623 href
: location
.origin
+ '/favicon.ico'
625 favicons
.push(favicon
);
632 * Checks whether an <object> node is plugin content (as <object> can also be
633 * used to embed images).
634 * @param {HTMLElement} node The <object> node to check.
635 * @return {boolean} Whether the node appears to be a plugin.
638 var objectNodeIsPlugin_ = function(node
) {
639 return node
.hasAttribute('classid') ||
640 (node
.hasAttribute('type') && node
.type
.indexOf('image/') != 0);
644 * Checks whether plugin a node has fallback content.
645 * @param {HTMLElement} node The node to check.
646 * @return {boolean} Whether the node has fallback.
649 var pluginHasFallbackContent_ = function(node
) {
650 return node
.textContent
.trim().length
> 0 ||
651 node
.getElementsByTagName('img').length
> 0;
655 * Returns a list of plugin elements in the document that have no fallback
656 * content. For nested plugins, only the innermost plugin element is returned.
657 * @return {Array} A list of plugin elements.
660 var findPluginNodesWithoutFallback_ = function() {
661 var pluginNodes
= [];
662 var objects
= document
.getElementsByTagName('object');
663 var objectCount
= objects
.length
;
664 for (var i
= 0; i
< objectCount
; i
++) {
665 var object
= objects
[i
];
666 if (objectNodeIsPlugin_(object
) &&
667 !pluginHasFallbackContent_(object
)) {
668 pluginNodes
.push(object
);
671 var applets
= document
.getElementsByTagName('applet');
672 var appletsCount
= applets
.length
;
673 for (var i
= 0; i
< appletsCount
; i
++) {
674 var applet
= applets
[i
];
675 if (!pluginHasFallbackContent_(applet
)) {
676 pluginNodes
.push(applet
);
683 * Finds and stores any plugins that don't have placeholders.
684 * Returns true if any plugins without placeholders are found.
686 __gCrWeb
.common
.updatePluginPlaceholders = function() {
687 var plugins
= findPluginNodesWithoutFallback_();
688 if (plugins
.length
> 0) {
689 // Store the list of plugins in a known place for the replacement script
690 // to use, then trigger it.
691 __gCrWeb
['placeholderTargetPlugins'] = plugins
;
696 }()); // End of anonymous object