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. */
21 // JSON safe object to protect against custom implementation of Object.toJSON
23 __gCrWeb['common'].JSONSafeObject = function JSONSafeObject() {
27 * Protect against custom implementation of Object.toJSON in host pages.
29 __gCrWeb['common'].JSONSafeObject.prototype.toJSON = null;
32 * Retain the original JSON.stringify method where possible to reduce the
33 * impact of sites overriding it
35 __gCrWeb.common.JSONStringify = JSON.stringify;
38 * Prefix used in references to form elements that have no 'id' or 'name'
40 __gCrWeb.common.kNamelessFormIDPrefix = 'gChrome~';
43 * Tests an element's visiblity. This test is expensive so should be used
45 * @param {Element} element A DOM element.
46 * @return {boolean} true if the |element| is currently part of the visible
49 __gCrWeb.common.isElementVisible = function(element) {
52 while (node && node !== document) {
53 if (node.nodeType === document.ELEMENT_NODE) {
54 var style = window.getComputedStyle(/** @type {Element} */(node));
55 if (style.display === 'none' || style.visibility === 'hidden') {
59 // Move up the tree and test again.
60 node = node.parentNode;
62 // Test reached the top of the DOM without finding a concealed ancestor.
67 * Based on Element::isFormControlElement() (WebKit)
68 * @param {Element} element A DOM element.
69 * @return {boolean} true if the |element| is a form control element.
71 __gCrWeb.common.isFormControlElement = function(element) {
72 var tagName = element.tagName;
73 return (tagName === 'INPUT' ||
74 tagName === 'SELECT' ||
75 tagName === 'TEXTAREA');
79 * Detects focusable elements.
80 * @param {Element} element A DOM element.
81 * @return {boolean} true if the |element| is focusable.
83 __gCrWeb.common.isFocusable = function(element) {
84 // When the disabled or hidden attributes are present, controls do not
86 if (element.hasAttribute('disabled') || element.hasAttribute('hidden'))
88 return __gCrWeb.common.isFormControlElement(element);
92 * Returns an array of control elements in a form.
94 * This method is based on the logic in method
95 * void WebFormElement::getFormControlElements(
96 * WebVector<WebFormControlElement>&) const
97 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
100 * @param {Element} form A form element for which the control elements are
102 * @return {Array<Element>}
104 __gCrWeb.common.getFormControlElements = function(form) {
109 // Get input and select elements from form.elements.
110 // TODO(chenyu): according to
111 // http://www.w3.org/TR/2011/WD-html5-20110525/forms.html, form.elements are
112 // the "listed elements whose form owner is the form element, with the
113 // exception of input elements whose type attribute is in the Image Button
114 // state, which must, for historical reasons, be excluded from this
115 // particular collection." In WebFormElement.cpp, this method is implemented
116 // by returning elements in form's associated elements that have tag 'INPUT'
117 // or 'SELECT'. Check if input Image Buttons are excluded in that
118 // implementation. Note for Autofill, as input Image Button is not
119 // considered as autofillable elements, there is no impact on Autofill
121 var elements = form.elements;
122 for (var i = 0; i < elements.length; i++) {
123 if (__gCrWeb.common.isFormControlElement(elements[i])) {
124 results.push(elements[i]);
131 * Returns true if an element can be autocompleted.
133 * This method aims to provide the same logic as method
134 * bool autoComplete() const
135 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
136 * WebFormElement.cpp.
138 * @param {Element} element An element to check if it can be autocompleted.
139 * @return {boolean} true if element can be autocompleted.
141 __gCrWeb.common.autoComplete = function(element) {
145 if (__gCrWeb.common.getLowerCaseAttribute(
146 element, 'autocomplete') == 'off') {
149 if (__gCrWeb.common.getLowerCaseAttribute(
150 element.form, 'autocomplete') == 'off') {
157 * Returns if an element is a text field.
158 * This returns true for all of textfield-looking types such as text,
159 * password, search, email, url, and number.
161 * This method aims to provide the same logic as method
162 * bool WebInputElement::isTextField() const
163 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
164 * WebInputElement.cpp, where this information is from
165 * bool HTMLInputElement::isTextField() const
167 * return m_inputType->isTextField();
169 * (chromium/src/third_party/WebKit/Source/WebCore/html/HTMLInputElement.cpp)
171 * The implementation here is based on the following:
173 * - Method bool InputType::isTextField() defaults to be false and it is
174 * override to return true only in HTMLInputElement's subclass
175 * TextFieldInputType (chromium/src/third_party/WebKit/Source/WebCore/html/
176 * TextFieldInputType.h).
178 * - The implementation here considers all the subclasses of
179 * TextFieldInputType: NumberInputType and BaseTextInputType, which has
180 * subclasses EmailInputType, PasswordInputType, SearchInputType,
181 * TelephoneInputType, TextInputType, URLInputType. (All these classes are
182 * defined in chromium/src/third_party/WebKit/Source/WebCore/html/)
184 * @param {Element} element An element to examine if it is a text field.
185 * @return {boolean} true if element has type=text.
187 __gCrWeb.common.isTextField = function(element) {
191 if (element.type === 'hidden') {
194 return element.type === 'text' ||
195 element.type === 'email' ||
196 element.type === 'password' ||
197 element.type === 'search' ||
198 element.type === 'tel' ||
199 element.type === 'url' ||
200 element.type === 'number';
204 * Sets the checked value of an input and dispatches an change event if
205 * |shouldSendChangeEvent|.
207 * This is a simplified version of the implementation of
209 * void setChecked(bool nowChecked, TextFieldEventBehavior eventBehavior)
211 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
212 * WebInputElement.cpp, which calls
213 * void HTMLInputElement::setChecked(
214 * bool nowChecked, TextFieldEventBehavior eventBehavior)
215 * in chromium/src/third_party/WebKit/Source/core/html/HTMLInputElement.cpp.
217 * @param {boolean} nowChecked The new checked value of the input element.
218 * @param {Element} input The input element of which the value is set.
219 * @param {boolean} shouldSendChangeEvent Whether a change event should be
222 __gCrWeb.common.setInputElementChecked = function(
223 nowChecked, input, shouldSendChangeEvent) {
224 var checkedChanged = input.checked !== nowChecked;
225 input.checked = nowChecked;
226 if (checkedChanged) {
227 __gCrWeb.common.createAndDispatchHTMLEvent(input, 'change', true, false);
232 * Sets the value of an input and dispatches an change event if
233 * |shouldSendChangeEvent|.
235 * It is based on the logic in
237 * void setValue(const WebString&, bool sendChangeEvent = false)
239 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/
240 * WebInputElement.cpp, which calls
241 * void setValue(const String& value, TextFieldEventBehavior eventBehavior)
242 * in chromium/src/third_party/WebKit/Source/core/html/HTMLInputElement.cpp.
244 * @param {string} value The value the input element will be set.
245 * @param {Element} input The input element of which the value is set.
246 * @param {boolean} shouldSendChangeEvent Whether a change event should be
249 __gCrWeb.common.setInputElementValue = function(
250 value, input, shouldSendChangeEvent) {
251 // In HTMLInputElement.cpp there is a check on canSetValue(value), which
252 // returns false only for file input. As file input is not relevant for
253 // autofill and this method is only used for autofill for now, there is no
254 // such check in this implementation.
255 var sanitizedValue = __gCrWeb.common.sanitizeValueForInputElement(
257 var valueChanged = sanitizedValue !== input.value;
258 input.value = sanitizedValue;
260 __gCrWeb.common.createAndDispatchHTMLEvent(input, 'change', true, false);
265 * Returns a sanitized value of proposedValue for a given input element type.
266 * The logic is based on
268 * String sanitizeValue(const String&) const
270 * in chromium/src/third_party/WebKit/Source/core/html/InputType.h
272 * @param {string} proposedValue The proposed value.
273 * @param {Element} element The element for which the proposedValue is to be
275 * @return {string} The sanitized value.
277 __gCrWeb.common.sanitizeValueForInputElement = function(
278 proposedValue, element) {
279 if (!proposedValue) {
283 // Method HTMLInputElement::sanitizeValue() calls InputType::sanitizeValue()
284 // (chromium/src/third_party/WebKit/Source/core/html/InputType.cpp) for
285 // non-null proposedValue. InputType::sanitizeValue() returns the original
286 // proposedValue by default and it is overridden in classes
287 // BaseDateAndTimeInputType, ColorInputType, RangeInputType and
288 // TextFieldInputType (all are in
289 // chromium/src/third_party/WebKit/Source/core/html/). Currently only
290 // TextFieldInputType is relevant and sanitizeValue() for other types of
291 // input elements has not been implemented.
292 if (__gCrWeb.common.isTextField(element)) {
293 return __gCrWeb.common.sanitizeValueForTextFieldInputType(
294 proposedValue, element);
296 return proposedValue;
300 * Returns a sanitized value for a text field.
302 * The logic is based on |String sanitizeValue(const String&)|
303 * in chromium/src/third_party/WebKit/Source/core/html/TextFieldInputType.h
304 * Note this method is overridden in EmailInputType and NumberInputType.
306 * @param {string} proposedValue The proposed value.
307 * @param {Element} element The element for which the proposedValue is to be
309 * @return {string} The sanitized value.
311 __gCrWeb.common.sanitizeValueForTextFieldInputType = function(
312 proposedValue, element) {
313 var textFieldElementType = element.type;
314 if (textFieldElementType === 'email') {
315 return __gCrWeb.common.sanitizeValueForEmailInputType(
316 proposedValue, element);
317 } else if (textFieldElementType === 'number') {
318 return __gCrWeb.common.sanitizeValueForNumberInputType(proposedValue);
320 var valueWithLineBreakRemoved = proposedValue.replace(/(\r\n|\n|\r)/gm, '');
321 // TODO(chenyu): Should we also implement numCharactersInGraphemeClusters()
322 // in chromium/src/third_party/WebKit/Source/core/platform/text/
323 // TextBreakIterator.cpp and call it here when computing newLength?
324 // Different from the implementation in TextFieldInputType.h, where a limit
325 // on the text length is considered due to
326 // https://bugs.webkit.org/show_bug.cgi?id=14536, no such limit is
327 // considered here for now.
328 var newLength = valueWithLineBreakRemoved.length;
329 // This logic is from method String limitLength() in TextFieldInputType.h
330 for (var i = 0; i < newLength; ++i) {
331 var current = valueWithLineBreakRemoved[i];
332 if (current < ' ' && current != '\t') {
337 return valueWithLineBreakRemoved.substring(0, newLength);
341 * Returns the sanitized value for an email input.
343 * The logic is based on
345 * String EmailInputType::sanitizeValue(const String& proposedValue) const
347 * in chromium/src/third_party/WebKit/Source/core/html/EmailInputType.cpp
349 * @param {string} proposedValue The proposed value.
350 * @param {Element} element The element for which the proposedValue is to be
352 * @return {string} The sanitized value.
354 __gCrWeb.common.sanitizeValueForEmailInputType = function(
355 proposedValue, element) {
356 var valueWithLineBreakRemoved = proposedValue.replace(/(\r\n|\n\r)/gm, '');
358 if (!element.multiple) {
359 return __gCrWeb.common.trim(proposedValue);
361 var addresses = valueWithLineBreakRemoved.split(',');
362 for (var i = 0; i < addresses.length; ++i) {
363 addresses[i] = __gCrWeb.common.trim(addresses[i]);
365 return addresses.join(',');
369 * Returns the sanitized value of a proposed value for a number input.
371 * The logic is based on
373 * String NumberInputType::sanitizeValue(const String& proposedValue)
376 * in chromium/src/third_party/WebKit/Source/core/html/NumberInputType.cpp
378 * Note in this implementation method Number() is used in the place of method
379 * parseToDoubleForNumberType() called in NumberInputType.cpp.
381 * @param {string} proposedValue The proposed value.
382 * @return {string} The sanitized value.
384 __gCrWeb.common.sanitizeValueForNumberInputType = function(proposedValue) {
385 var sanitizedValue = Number(proposedValue);
386 if (isNaN(sanitizedValue)) {
389 return sanitizedValue.toString();
393 * Trims any whitespace from the start and end of a string.
394 * Used in preference to String.prototype.trim as this can be overridden by
397 * @param {string} str The string to be trimmed.
398 * @return {string} The string after trimming.
400 __gCrWeb.common.trim = function(str) {
401 return str.replace(/^\s+|\s+$/g, '');
405 * Returns the name that should be used for the specified |element| when
406 * storing Autofill data. Various attributes are used to attempt to identify
407 * the element, beginning with 'name' and 'id' attributes. Providing a
408 * uniquely reversible identifier for any element is a non-trivial problem;
409 * this solution attempts to satisfy the majority of cases.
411 * It aims to provide the logic in
412 * WebString nameForAutofill() const;
413 * in chromium/src/third_party/WebKit/Source/WebKit/chromium/public/
414 * WebFormControlElement.h
416 * @param {Element} element An element of which the name for Autofill will be
418 * @return {string} the name for Autofill.
420 __gCrWeb.common.nameForAutofill = function(element) {
424 var trimmedName = element.name;
426 trimmedName = __gCrWeb.common.trim(trimmedName);
427 if (trimmedName.length > 0) {
431 trimmedName = element.getAttribute('id');
433 return __gCrWeb.common.trim(trimmedName);
439 * Acquires the specified DOM |attribute| from the DOM |element| and returns
440 * its lower-case value, or null if not present.
441 * @param {Element} element A DOM element.
442 * @param {string} attribute An attribute name.
443 * @return {?string} Lowercase value of DOM element or null if not present.
445 __gCrWeb.common.getLowerCaseAttribute = function(element, attribute) {
449 var value = element.getAttribute(attribute);
451 return value.toLowerCase();
457 * Converts a relative URL into an absolute URL.
458 * @param {Object} doc Document.
459 * @param {string} relativeURL Relative URL.
460 * @return {string} Absolute URL.
462 __gCrWeb.common.absoluteURL = function(doc, relativeURL) {
463 // In the case of data: URL-based pages, relativeURL === absoluteURL.
464 if (doc.location.protocol === 'data:') {
465 return doc.location.href;
467 var urlNormalizer = doc['__gCrWebURLNormalizer'];
468 if (!urlNormalizer) {
469 urlNormalizer = doc.createElement('a');
470 doc['__gCrWebURLNormalizer'] = urlNormalizer;
473 // Use the magical quality of the <a> element. It automatically converts
474 // relative URLs into absolute ones.
475 urlNormalizer.href = relativeURL;
476 return urlNormalizer.href;
480 * Extracts the webpage URL from the given URL by removing the query
481 * and the reference (aka fragment) from the URL.
482 * @param {string} url Web page URL.
483 * @return {string} Web page URL with query and reference removed.
485 __gCrWeb.common.removeQueryAndReferenceFromURL = function(url) {
486 var queryIndex = url.indexOf('?');
487 if (queryIndex != -1) {
488 return url.substring(0, queryIndex);
491 var hashIndex = url.indexOf('#');
492 if (hashIndex != -1) {
493 return url.substring(0, hashIndex);
499 * Returns the form's |name| attribute if non-empty; otherwise the form's |id|
500 * attribute, or the index of the form (with prefix) in document.forms.
502 * It is partially based on the logic in
503 * const string16 GetFormIdentifier(const blink::WebFormElement& form)
504 * in chromium/src/components/autofill/renderer/form_autofill_util.h.
506 * @param {Element} form An element for which the identifier is returned.
507 * @return {string} a string that represents the element's identifier.
509 __gCrWeb.common.getFormIdentifier = function(form) {
512 var name = form.getAttribute('name');
513 if (name && name.length != 0) {
516 name = form.getAttribute('id');
520 // A form name must be supplied, because the element will later need to be
521 // identified from the name. A last resort is to take the index number of
522 // the form in document.forms. ids are not supposed to begin with digits (by
523 // HTML 4 spec) so this is unlikely to match a true id.
524 for (var idx = 0; idx != document.forms.length; idx++) {
525 if (document.forms[idx] == form) {
526 return __gCrWeb.common.kNamelessFormIDPrefix + idx;
533 * Returns the form element from an ID obtained from getFormIdentifier.
535 * This works on a 'best effort' basis since DOM changes can always change the
536 * actual element that the ID refers to.
538 * @param {string} name An ID string obtained via getFormIdentifier.
539 * @return {Element} The original form element, if it can be determined.
541 __gCrWeb.common.getFormElementFromIdentifier = function(name) {
542 // First attempt is from the name / id supplied.
543 var form = document.forms.namedItem(name);
547 // Second attempt is from the prefixed index position of the form in
549 if (name.indexOf(__gCrWeb.common.kNamelessFormIDPrefix) == 0) {
550 var nameAsInteger = 0 |
551 name.substring(__gCrWeb.common.kNamelessFormIDPrefix.length);
552 if (__gCrWeb.common.kNamelessFormIDPrefix + nameAsInteger == name &&
553 nameAsInteger < document.forms.length) {
554 return document.forms[nameAsInteger];
561 * Creates and dispatches an HTML event.
563 * @param {Element} element The element for which an event is created.
564 * @param {string} type The type of the event.
565 * @param {boolean} bubbles A boolean indicating whether the event should
566 * bubble up through the event chain or not.
567 * @param {boolean} cancelable A boolean indicating whether the event can be
570 __gCrWeb.common.createAndDispatchHTMLEvent = function(
571 element, type, bubbles, cancelable) {
572 var changeEvent = element.ownerDocument.createEvent('HTMLEvents');
573 changeEvent.initEvent(type, bubbles, cancelable);
575 // A timer is used to avoid reentering JavaScript evaluation.
576 window.setTimeout(function() {
577 element.dispatchEvent(changeEvent);
582 * Retrieves favicon information.
584 * @return {Object} Object containing favicon data.
586 __gCrWeb.common.getFavicons = function() {
588 var hasFavicon = false;
589 favicons.toJSON = null; // Never inherit Array.prototype.toJSON.
590 var links = document.getElementsByTagName('link');
591 var linkCount = links.length;
592 for (var i = 0; i < linkCount; ++i) {
594 var rel = links[i].rel.toLowerCase();
595 if (rel == 'shortcut icon' ||
597 rel == 'apple-touch-icon' ||
598 rel == 'apple-touch-icon-precomposed') {
600 rel: links[i].rel.toLowerCase(),
603 favicons.push(favicon);
604 if (rel == 'icon' || rel == 'shortcut icon') {
611 // If an HTTP(S)? webpage does not reference a "favicon" then search
612 // for a file named "favicon.ico" at the root of the website (legacy).
613 // http://en.wikipedia.org/wiki/Favicon
614 var location = document.location;
615 if (location.protocol == 'http:' || location.protocol == 'https:') {
618 href: location.origin + '/favicon.ico'
620 favicons.push(favicon);
627 * Checks whether an <object> node is plugin content (as <object> can also be
628 * used to embed images).
629 * @param {HTMLElement} node The <object> node to check.
630 * @return {boolean} Whether the node appears to be a plugin.
633 var objectNodeIsPlugin_ = function(node) {
634 return node.hasAttribute('classid') ||
635 (node.hasAttribute('type') && node.type.indexOf('image/') != 0);
639 * Checks whether plugin a node has fallback content.
640 * @param {HTMLElement} node The node to check.
641 * @return {boolean} Whether the node has fallback.
644 var pluginHasFallbackContent_ = function(node) {
645 return node.textContent.trim().length > 0 ||
646 node.getElementsByTagName('img').length > 0;
650 * Returns a list of plugin elements in the document that have no fallback
651 * content. For nested plugins, only the innermost plugin element is returned.
652 * @return {Array} A list of plugin elements.
655 var findPluginNodesWithoutFallback_ = function() {
656 var pluginNodes = [];
657 var objects = document.getElementsByTagName('object');
658 var objectCount = objects.length;
659 for (var i = 0; i < objectCount; i++) {
660 var object = objects[i];
661 if (objectNodeIsPlugin_(object) &&
662 !pluginHasFallbackContent_(object)) {
663 pluginNodes.push(object);
666 var applets = document.getElementsByTagName('applet');
667 var appletsCount = applets.length;
668 for (var i = 0; i < appletsCount; i++) {
669 var applet = applets[i];
670 if (!pluginHasFallbackContent_(applet)) {
671 pluginNodes.push(applet);
678 * Finds and stores any plugins that don't have placeholders.
679 * Returns true if any plugins without placeholders are found.
681 __gCrWeb.common.updatePluginPlaceholders = function() {
682 var plugins = findPluginNodesWithoutFallback_();
683 if (plugins.length > 0) {
684 // Store the list of plugins in a known place for the replacement script
685 // to use, then trigger it.
686 __gCrWeb['placeholderTargetPlugins'] = plugins;
691 } // End of anonymous object