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