| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This file provides common methods that can be shared by other JavaScripts. |
| |
| goog.provide('__crWeb.common'); |
| |
| goog.require('__crWeb.base'); |
| |
| |
| /** @typedef {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement} */ |
| var FormControlElement; |
| |
| /** |
| * Namespace for this file. It depends on |__gCrWeb| having already been |
| * injected. String 'common' is used in |__gCrWeb['common']| as it needs to be |
| * accessed in Objective-C code. |
| */ |
| __gCrWeb.common = {}; |
| |
| // Store common namespace object in a global __gCrWeb object referenced by a |
| // string, so it does not get renamed by closure compiler during the |
| // minification. |
| __gCrWeb['common'] = __gCrWeb.common; |
| |
| /* Beginning of anonymous object. */ |
| (function() { |
| |
| /** |
| * JSON safe object to protect against custom implementation of Object.toJSON |
| * in host pages. |
| * @constructor |
| */ |
| __gCrWeb.common.JSONSafeObject = function JSONSafeObject() { |
| }; |
| |
| /** |
| * Protect against custom implementation of Object.toJSON in host pages. |
| */ |
| __gCrWeb.common.JSONSafeObject.prototype['toJSON'] = null; |
| |
| /** |
| * Retain the original JSON.stringify method where possible to reduce the |
| * impact of sites overriding it |
| */ |
| __gCrWeb.common.JSONStringify = JSON.stringify; |
| |
| /** |
| * Returns a string that is formatted according to the JSON syntax rules. |
| * This is equivalent to the built-in JSON.stringify() function, but is |
| * less likely to be overridden by the website itself. Prefer the private |
| * {@code __gcrWeb.common.JSONStringify} whenever possible instead of using |
| * this public function. The |__gCrWeb| object itself does not use it; it uses |
| * its private counterpart instead. |
| */ |
| __gCrWeb['stringify'] = function(value) { |
| if (value === null) |
| return 'null'; |
| if (value === undefined) |
| return 'undefined'; |
| if (typeof(value.toJSON) == 'function') { |
| // Prevents websites from changing stringify's behavior by adding the |
| // method toJSON() by temporarily removing it. |
| var originalToJSON = value.toJSON; |
| value.toJSON = undefined; |
| var stringifiedValue = __gCrWeb.common.JSONStringify(value); |
| value.toJSON = originalToJSON; |
| return stringifiedValue; |
| } |
| return __gCrWeb.common.JSONStringify(value); |
| }; |
| |
| /** |
| * Prefix used in references to form elements that have no 'id' or 'name' |
| */ |
| __gCrWeb.common.kNamelessFormIDPrefix = 'gChrome~form~'; |
| |
| /** |
| * Prefix used in references to field elements that have no 'id' or 'name' but |
| * are included in a form. |
| */ |
| __gCrWeb.common.kNamelessFieldIDPrefix = 'gChrome~field~'; |
| |
| /** |
| * Tests an element's visiblity. This test is expensive so should be used |
| * sparingly. |
| * @param {Element} element A DOM element. |
| * @return {boolean} true if the |element| is currently part of the visible |
| * DOM. |
| */ |
| __gCrWeb.common.isElementVisible = function(element) { |
| /** @type {Node} */ |
| var node = element; |
| while (node && node !== document) { |
| if (node.nodeType === Node.ELEMENT_NODE) { |
| var style = window.getComputedStyle(/** @type {Element} */(node)); |
| if (style.display === 'none' || style.visibility === 'hidden') { |
| return false; |
| } |
| } |
| // Move up the tree and test again. |
| node = node.parentNode; |
| } |
| // Test reached the top of the DOM without finding a concealed ancestor. |
| return true; |
| }; |
| |
| /** |
| * Based on Element::isFormControlElement() (WebKit) |
| * @param {Element} element A DOM element. |
| * @return {boolean} true if the |element| is a form control element. |
| */ |
| __gCrWeb.common.isFormControlElement = function(element) { |
| var tagName = element.tagName; |
| return (tagName === 'INPUT' || |
| tagName === 'SELECT' || |
| tagName === 'TEXTAREA'); |
| }; |
| |
| /** |
| * Returns an array of control elements in a form. |
| * |
| * This method is based on the logic in method |
| * void WebFormElement::getFormControlElements( |
| * WebVector<WebFormControlElement>&) const |
| * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/ |
| * WebFormElement.cpp. |
| * |
| * @param {Element} form A form element for which the control elements are |
| * returned. |
| * @return {Array<FormControlElement>} |
| */ |
| __gCrWeb.common.getFormControlElements = function(form) { |
| if (!form) { |
| return []; |
| } |
| var results = []; |
| // Get input and select elements from form.elements. |
| // TODO(chenyu): according to |
| // http://www.w3.org/TR/2011/WD-html5-20110525/forms.html, form.elements are |
| // the "listed elements whose form owner is the form element, with the |
| // exception of input elements whose type attribute is in the Image Button |
| // state, which must, for historical reasons, be excluded from this |
| // particular collection." In WebFormElement.cpp, this method is implemented |
| // by returning elements in form's associated elements that have tag 'INPUT' |
| // or 'SELECT'. Check if input Image Buttons are excluded in that |
| // implementation. Note for Autofill, as input Image Button is not |
| // considered as autofillable elements, there is no impact on Autofill |
| // feature. |
| var elements = form.elements; |
| for (var i = 0; i < elements.length; i++) { |
| if (__gCrWeb.common.isFormControlElement(elements[i])) { |
| results.push(/** @type {FormControlElement} */ (elements[i])); |
| } |
| } |
| return results; |
| }; |
| |
| /** |
| * Returns true if an element can be autocompleted. |
| * |
| * This method aims to provide the same logic as method |
| * bool autoComplete() const |
| * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/ |
| * WebFormElement.cpp. |
| * |
| * @param {Element} element An element to check if it can be autocompleted. |
| * @return {boolean} true if element can be autocompleted. |
| */ |
| __gCrWeb.common.autoComplete = function(element) { |
| if (!element) { |
| return false; |
| } |
| if (__gCrWeb.common.getLowerCaseAttribute( |
| element, 'autocomplete') == 'off') { |
| return false; |
| } |
| if (__gCrWeb.common.getLowerCaseAttribute( |
| element.form, 'autocomplete') == 'off') { |
| return false; |
| } |
| return true; |
| }; |
| |
| /** |
| * Returns if an element is a text field. |
| * This returns true for all of textfield-looking types such as text, |
| * password, search, email, url, and number. |
| * |
| * This method aims to provide the same logic as method |
| * bool WebInputElement::isTextField() const |
| * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/ |
| * WebInputElement.cpp, where this information is from |
| * bool HTMLInputElement::isTextField() const |
| * { |
| * return m_inputType->isTextField(); |
| * } |
| * (chromium/src/third_party/WebKit/Source/WebCore/html/HTMLInputElement.cpp) |
| * |
| * The implementation here is based on the following: |
| * |
| * - Method bool InputType::isTextField() defaults to be false and it is |
| * override to return true only in HTMLInputElement's subclass |
| * TextFieldInputType (chromium/src/third_party/WebKit/Source/WebCore/html/ |
| * TextFieldInputType.h). |
| * |
| * - The implementation here considers all the subclasses of |
| * TextFieldInputType: NumberInputType and BaseTextInputType, which has |
| * subclasses EmailInputType, PasswordInputType, SearchInputType, |
| * TelephoneInputType, TextInputType, URLInputType. (All these classes are |
| * defined in chromium/src/third_party/WebKit/Source/WebCore/html/) |
| * |
| * @param {Element} element An element to examine if it is a text field. |
| * @return {boolean} true if element has type=text. |
| */ |
| __gCrWeb.common.isTextField = function(element) { |
| if (!element) { |
| return false; |
| } |
| if (element.type === 'hidden') { |
| return false; |
| } |
| return element.type === 'text' || |
| element.type === 'email' || |
| element.type === 'password' || |
| element.type === 'search' || |
| element.type === 'tel' || |
| element.type === 'url' || |
| element.type === 'number'; |
| }; |
| |
| /** |
| * Sets the value of a data-bound input using AngularJS. |
| * |
| * The method first set the value using the val() method. Then, if input is |
| * bound to a model value, it sets the model value. |
| * Documentation of relevant modules of AngularJS can be found at |
| * https://docs.angularjs.org/guide/databinding |
| * https://docs.angularjs.org/api/auto/service/$injector |
| * https://docs.angularjs.org/api/ng/service/$parse |
| * |
| * @param {string} value The value the input element will be set. |
| * @param {Element} input The input element of which the value is set. |
| **/ |
| function setInputElementAngularValue_(value, input) { |
| if (!input || !window['angular']) { |
| return; |
| } |
| var angular_element = window['angular'].element && |
| window['angular'].element(input); |
| if (!angular_element) { |
| return; |
| } |
| angular_element.val(value); |
| var angular_model = angular_element.data && angular_element.data('ngModel'); |
| if (!angular_model) { |
| return; |
| } |
| angular_element.injector().invoke(['$parse', function(parse) { |
| var setter = parse(angular_model); |
| setter.assign(angular_element.scope(), value); |
| }]) |
| } |
| |
| /** |
| * Sets the value of an input and dispatches a change event if |
| * |shouldSendChangeEvent|. |
| * |
| * It is based on the logic in |
| * |
| * void setValue(const WebString&, bool sendChangeEvent = false) |
| * |
| * in chromium/src/third_party/WebKit/Source/WebKit/chromium/src/ |
| * WebInputElement.cpp, which calls |
| * void setValue(const String& value, TextFieldEventBehavior eventBehavior) |
| * or |
| * void setChecked(bool nowChecked, TextFieldEventBehavior eventBehavior) |
| * in chromium/src/third_party/WebKit/Source/core/html/HTMLInputElement.cpp. |
| * |
| * @param {string} value The value the input element will be set. |
| * @param {Element} input The input element of which the value is set. |
| * @param {boolean} shouldSendChangeEvent Whether a change event should be |
| * dispatched. |
| */ |
| __gCrWeb.common.setInputElementValue = function( |
| value, input, shouldSendChangeEvent) { |
| if (!input) { |
| return; |
| } |
| var changed = false; |
| if (input.type === 'checkbox' || input.type === 'radio') { |
| changed = input.checked !== value; |
| input.checked = value; |
| } else if (input.type === 'select-one') { |
| changed = input.value !== value; |
| input.value = value; |
| } else { |
| // In HTMLInputElement.cpp there is a check on canSetValue(value), which |
| // returns false only for file input. As file input is not relevant for |
| // autofill and this method is only used for autofill for now, there is no |
| // such check in this implementation. |
| var sanitizedValue = __gCrWeb.common.sanitizeValueForInputElement( |
| value, input); |
| changed = sanitizedValue !== input.value; |
| input.value = sanitizedValue; |
| } |
| if (window['angular']) { |
| // The page uses the AngularJS framework. Update the angular value before |
| // sending events. |
| setInputElementAngularValue_(value, input); |
| } |
| if (changed && shouldSendChangeEvent) { |
| __gCrWeb.common.notifyElementValueChanged(input); |
| } |
| }; |
| |
| /** |
| * Returns a sanitized value of proposedValue for a given input element type. |
| * The logic is based on |
| * |
| * String sanitizeValue(const String&) const |
| * |
| * in chromium/src/third_party/WebKit/Source/core/html/InputType.h |
| * |
| * @param {string} proposedValue The proposed value. |
| * @param {Element} element The element for which the proposedValue is to be |
| * sanitized. |
| * @return {string} The sanitized value. |
| */ |
| __gCrWeb.common.sanitizeValueForInputElement = function( |
| proposedValue, element) { |
| if (!proposedValue) { |
| return ''; |
| } |
| |
| // Method HTMLInputElement::sanitizeValue() calls InputType::sanitizeValue() |
| // (chromium/src/third_party/WebKit/Source/core/html/InputType.cpp) for |
| // non-null proposedValue. InputType::sanitizeValue() returns the original |
| // proposedValue by default and it is overridden in classes |
| // BaseDateAndTimeInputType, ColorInputType, RangeInputType and |
| // TextFieldInputType (all are in |
| // chromium/src/third_party/WebKit/Source/core/html/). Currently only |
| // TextFieldInputType is relevant and sanitizeValue() for other types of |
| // input elements has not been implemented. |
| if (__gCrWeb.common.isTextField(element)) { |
| return __gCrWeb.common.sanitizeValueForTextFieldInputType( |
| proposedValue, element); |
| } |
| return proposedValue; |
| }; |
| |
| /** |
| * Returns a sanitized value for a text field. |
| * |
| * The logic is based on |String sanitizeValue(const String&)| |
| * in chromium/src/third_party/WebKit/Source/core/html/TextFieldInputType.h |
| * Note this method is overridden in EmailInputType and NumberInputType. |
| * |
| * @param {string} proposedValue The proposed value. |
| * @param {Element} element The element for which the proposedValue is to be |
| * sanitized. |
| * @return {string} The sanitized value. |
| */ |
| __gCrWeb.common.sanitizeValueForTextFieldInputType = function( |
| proposedValue, element) { |
| var textFieldElementType = element.type; |
| if (textFieldElementType === 'email') { |
| return __gCrWeb.common.sanitizeValueForEmailInputType( |
| proposedValue, element); |
| } else if (textFieldElementType === 'number') { |
| return __gCrWeb.common.sanitizeValueForNumberInputType(proposedValue); |
| } |
| var valueWithLineBreakRemoved = proposedValue.replace(/(\r\n|\n|\r)/gm, ''); |
| // TODO(chenyu): Should we also implement numCharactersInGraphemeClusters() |
| // in chromium/src/third_party/WebKit/Source/core/platform/text/ |
| // TextBreakIterator.cpp and call it here when computing newLength? |
| // Different from the implementation in TextFieldInputType.h, where a limit |
| // on the text length is considered due to |
| // https://bugs.webkit.org/show_bug.cgi?id=14536, no such limit is |
| // considered here for now. |
| var newLength = valueWithLineBreakRemoved.length; |
| // This logic is from method String limitLength() in TextFieldInputType.h |
| for (var i = 0; i < newLength; ++i) { |
| var current = valueWithLineBreakRemoved[i]; |
| if (current < ' ' && current != '\t') { |
| newLength = i; |
| break; |
| } |
| } |
| return valueWithLineBreakRemoved.substring(0, newLength); |
| }; |
| |
| /** |
| * Returns the sanitized value for an email input. |
| * |
| * The logic is based on |
| * |
| * String EmailInputType::sanitizeValue(const String& proposedValue) const |
| * |
| * in chromium/src/third_party/WebKit/Source/core/html/EmailInputType.cpp |
| * |
| * @param {string} proposedValue The proposed value. |
| * @param {Element} element The element for which the proposedValue is to be |
| * sanitized. |
| * @return {string} The sanitized value. |
| */ |
| __gCrWeb.common.sanitizeValueForEmailInputType = function( |
| proposedValue, element) { |
| var valueWithLineBreakRemoved = proposedValue.replace(/(\r\n|\n\r)/gm, ''); |
| |
| if (!element.multiple) { |
| return __gCrWeb.common.trim(proposedValue); |
| } |
| var addresses = valueWithLineBreakRemoved.split(','); |
| for (var i = 0; i < addresses.length; ++i) { |
| addresses[i] = __gCrWeb.common.trim(addresses[i]); |
| } |
| return addresses.join(','); |
| }; |
| |
| /** |
| * Returns the sanitized value of a proposed value for a number input. |
| * |
| * The logic is based on |
| * |
| * String NumberInputType::sanitizeValue(const String& proposedValue) |
| * const |
| * |
| * in chromium/src/third_party/WebKit/Source/core/html/NumberInputType.cpp |
| * |
| * Note in this implementation method Number() is used in the place of method |
| * parseToDoubleForNumberType() called in NumberInputType.cpp. |
| * |
| * @param {string} proposedValue The proposed value. |
| * @return {string} The sanitized value. |
| */ |
| __gCrWeb.common.sanitizeValueForNumberInputType = function(proposedValue) { |
| var sanitizedValue = Number(proposedValue); |
| if (isNaN(sanitizedValue)) { |
| return ''; |
| } |
| return sanitizedValue.toString(); |
| }; |
| |
| /** |
| * Trims any whitespace from the start and end of a string. |
| * Used in preference to String.prototype.trim as this can be overridden by |
| * sites. |
| * |
| * @param {string} str The string to be trimmed. |
| * @return {string} The string after trimming. |
| */ |
| __gCrWeb.common.trim = function(str) { |
| return str.replace(/^\s+|\s+$/g, ''); |
| }; |
| |
| /** |
| * Returns the form's |name| attribute if not space only; otherwise the |
| * form's |id| attribute. |
| * |
| * It is the name that should be used for the specified |element| when |
| * storing Autofill data. Various attributes are used to attempt to identify |
| * the element, beginning with 'name' and 'id' attributes. If both name and id |
| * are empty and the field is in a form, returns |
| * __gCrWeb.common.kNamelessFieldIDPrefix + index of the field in the form. |
| * Providing a uniquely reversible identifier for any element is a non-trivial |
| * problem; this solution attempts to satisfy the majority of cases. |
| * |
| * It aims to provide the logic in |
| * WebString nameForAutofill() const; |
| * in chromium/src/third_party/WebKit/Source/WebKit/chromium/public/ |
| * WebFormControlElement.h |
| * |
| * @param {Element} element An element of which the name for Autofill will be |
| * returned. |
| * @return {string} the name for Autofill. |
| */ |
| __gCrWeb.common.getFieldIdentifier = function(element) { |
| if (!element) { |
| return ''; |
| } |
| var trimmedName = element.name; |
| if (trimmedName) { |
| trimmedName = __gCrWeb.common.trim(trimmedName); |
| if (trimmedName.length > 0) { |
| return trimmedName; |
| } |
| } |
| trimmedName = element.id; |
| if (trimmedName) { |
| return __gCrWeb.common.trim(trimmedName); |
| } |
| if (element.form) { |
| var elements = __gCrWeb.common.getFormControlElements(element.form); |
| for (var index = 0; index < elements.length; index++) { |
| if (elements[index] === element) { |
| return __gCrWeb.common.kNamelessFieldIDPrefix + index; |
| } |
| } |
| } |
| return ''; |
| }; |
| |
| /** |
| * Acquires the specified DOM |attribute| from the DOM |element| and returns |
| * its lower-case value, or null if not present. |
| * @param {Element} element A DOM element. |
| * @param {string} attribute An attribute name. |
| * @return {?string} Lowercase value of DOM element or null if not present. |
| */ |
| __gCrWeb.common.getLowerCaseAttribute = function(element, attribute) { |
| if (!element) { |
| return null; |
| } |
| var value = element.getAttribute(attribute); |
| if (value) { |
| return value.toLowerCase(); |
| } |
| return null; |
| }; |
| |
| /** |
| * Converts a relative URL into an absolute URL. |
| * @param {Object} doc Document. |
| * @param {string} relativeURL Relative URL. |
| * @return {string} Absolute URL. |
| */ |
| __gCrWeb.common.absoluteURL = function(doc, relativeURL) { |
| // In the case of data: URL-based pages, relativeURL === absoluteURL. |
| if (doc.location.protocol === 'data:') { |
| return doc.location.href; |
| } |
| var urlNormalizer = doc['__gCrWebURLNormalizer']; |
| if (!urlNormalizer) { |
| urlNormalizer = doc.createElement('a'); |
| doc['__gCrWebURLNormalizer'] = urlNormalizer; |
| } |
| |
| // Use the magical quality of the <a> element. It automatically converts |
| // relative URLs into absolute ones. |
| urlNormalizer.href = relativeURL; |
| return urlNormalizer.href; |
| }; |
| |
| /** |
| * Extracts the webpage URL from the given URL by removing the query |
| * and the reference (aka fragment) from the URL. |
| * @param {string} url Web page URL. |
| * @return {string} Web page URL with query and reference removed. |
| */ |
| __gCrWeb.common.removeQueryAndReferenceFromURL = function(url) { |
| var parsed = new URL(url); |
| // For some protocols (eg. data:, javascript:) URL.origin is "null" so |
| // URL.protocol is used instead. |
| return (parsed.origin !== "null" ? parsed.origin : parsed.protocol) |
| + parsed.pathname; |
| }; |
| |
| /** |
| * Returns the form's |name| attribute if non-empty; otherwise the form's |id| |
| * attribute, or the index of the form (with prefix) in document.forms. |
| * |
| * It is partially based on the logic in |
| * const string16 GetFormIdentifier(const blink::WebFormElement& form) |
| * in chromium/src/components/autofill/renderer/form_autofill_util.h. |
| * |
| * @param {Element} form An element for which the identifier is returned. |
| * @return {string} a string that represents the element's identifier. |
| */ |
| __gCrWeb.common.getFormIdentifier = function(form) { |
| if (!form) |
| return ''; |
| var name = form.getAttribute('name'); |
| if (name && name.length != 0 && |
| form.ownerDocument.forms.namedItem(name) === form) { |
| return name; |
| } |
| name = form.getAttribute('id'); |
| if (name) { |
| return name; |
| } |
| // A form name must be supplied, because the element will later need to be |
| // identified from the name. A last resort is to take the index number of |
| // the form in document.forms. ids are not supposed to begin with digits (by |
| // HTML 4 spec) so this is unlikely to match a true id. |
| for (var idx = 0; idx != document.forms.length; idx++) { |
| if (document.forms[idx] == form) { |
| return __gCrWeb.common.kNamelessFormIDPrefix + idx; |
| } |
| } |
| return ''; |
| }; |
| |
| /** |
| * Returns the form element from an ID obtained from getFormIdentifier. |
| * |
| * This works on a 'best effort' basis since DOM changes can always change the |
| * actual element that the ID refers to. |
| * |
| * @param {string} name An ID string obtained via getFormIdentifier. |
| * @return {Element} The original form element, if it can be determined. |
| */ |
| __gCrWeb.common.getFormElementFromIdentifier = function(name) { |
| // First attempt is from the name / id supplied. |
| var form = document.forms.namedItem(name); |
| if (form) { |
| if (form.nodeType !== Node.ELEMENT_NODE) |
| return null; |
| return /** @type {Element} */(form); |
| } |
| // Second attempt is from the prefixed index position of the form in |
| // document.forms. |
| if (name.indexOf(__gCrWeb.common.kNamelessFormIDPrefix) == 0) { |
| var nameAsInteger = 0 | |
| name.substring(__gCrWeb.common.kNamelessFormIDPrefix.length); |
| if (__gCrWeb.common.kNamelessFormIDPrefix + nameAsInteger == name && |
| nameAsInteger < document.forms.length) { |
| return document.forms[nameAsInteger]; |
| } |
| } |
| return null; |
| }; |
| |
| /** |
| * Creates and sends notification that element has changed. |
| * |
| * Most handlers react to 'change' or 'input' event, so sent both. |
| * |
| * @param {Element} element The element that changed. |
| */ |
| __gCrWeb.common.notifyElementValueChanged = function(element) { |
| __gCrWeb.common.createAndDispatchHTMLEvent(element, 'keydown', true, false); |
| __gCrWeb.common.createAndDispatchHTMLEvent(element, 'change', true, false); |
| __gCrWeb.common.createAndDispatchHTMLEvent(element, 'input', true, false); |
| __gCrWeb.common.createAndDispatchHTMLEvent(element, 'keyup', true, false); |
| }; |
| |
| /** |
| * Creates and dispatches an HTML event. |
| * |
| * @param {Element} element The element for which an event is created. |
| * @param {string} type The type of the event. |
| * @param {boolean} bubbles A boolean indicating whether the event should |
| * bubble up through the event chain or not. |
| * @param {boolean} cancelable A boolean indicating whether the event can be |
| * canceled. |
| */ |
| __gCrWeb.common.createAndDispatchHTMLEvent = function( |
| element, type, bubbles, cancelable) { |
| var changeEvent = |
| /** @type {!Event} */(element.ownerDocument.createEvent('HTMLEvents')); |
| changeEvent.initEvent(type, bubbles, cancelable); |
| // Some frameworks will use the data field to update their cache value. |
| changeEvent.data = element.value; |
| |
| // A timer is used to avoid reentering JavaScript evaluation. |
| window.setTimeout(function() { |
| element.dispatchEvent(changeEvent); |
| }, 0); |
| }; |
| |
| /** |
| * Retrieves favicon information. |
| * |
| * @return {Object} Object containing favicon data. |
| */ |
| __gCrWeb.common.getFavicons = function() { |
| var favicons = []; |
| var hasFavicon = false; |
| delete favicons.toJSON; // Never inherit Array.prototype.toJSON. |
| var links = document.getElementsByTagName('link'); |
| var linkCount = links.length; |
| for (var i = 0; i < linkCount; ++i) { |
| if (links[i].rel) { |
| var rel = links[i].rel.toLowerCase(); |
| if (rel == 'shortcut icon' || |
| rel == 'icon' || |
| rel == 'apple-touch-icon' || |
| rel == 'apple-touch-icon-precomposed') { |
| var favicon = { |
| rel: links[i].rel.toLowerCase(), |
| href: links[i].href |
| }; |
| favicons.push(favicon); |
| if (rel == 'icon' || rel == 'shortcut icon') { |
| hasFavicon = true; |
| } |
| } |
| } |
| } |
| return favicons; |
| }; |
| |
| /** |
| * Checks whether an <object> node is plugin content (as <object> can also be |
| * used to embed images). |
| * @param {HTMLElement} node The <object> node to check. |
| * @return {boolean} Whether the node appears to be a plugin. |
| * @private |
| */ |
| var objectNodeIsPlugin_ = function(node) { |
| return node.hasAttribute('classid') || |
| (node.hasAttribute('type') && node.type.indexOf('image/') != 0); |
| }; |
| |
| /** |
| * Checks whether plugin a node has fallback content. |
| * @param {HTMLElement} node The node to check. |
| * @return {boolean} Whether the node has fallback. |
| * @private |
| */ |
| var pluginHasFallbackContent_ = function(node) { |
| return node.textContent.trim().length > 0 || |
| node.getElementsByTagName('img').length > 0; |
| }; |
| |
| /** |
| * Returns a list of plugin elements in the document that have no fallback |
| * content. For nested plugins, only the innermost plugin element is returned. |
| * @return {!Array<!HTMLElement>} A list of plugin elements. |
| * @private |
| */ |
| var findPluginNodesWithoutFallback_ = function() { |
| var i, pluginNodes = []; |
| var objects = document.getElementsByTagName('object'); |
| var objectCount = objects.length; |
| for (i = 0; i < objectCount; i++) { |
| var object = /** @type {!HTMLElement} */(objects[i]); |
| if (objectNodeIsPlugin_(object) && |
| !pluginHasFallbackContent_(object)) { |
| pluginNodes.push(object); |
| } |
| } |
| var applets = document.getElementsByTagName('applet'); |
| var appletsCount = applets.length; |
| for (i = 0; i < appletsCount; i++) { |
| var applet = /** @type {!HTMLElement} */(applets[i]); |
| if (!pluginHasFallbackContent_(applet)) { |
| pluginNodes.push(applet); |
| } |
| } |
| return pluginNodes; |
| }; |
| |
| /** |
| * Finds and stores any plugins that don't have placeholders. |
| * Returns true if any plugins without placeholders are found. |
| */ |
| __gCrWeb.common.updatePluginPlaceholders = function() { |
| var plugins = findPluginNodesWithoutFallback_(); |
| if (plugins.length > 0) { |
| // Store the list of plugins in a known place for the replacement script |
| // to use, then trigger it. |
| __gCrWeb['placeholderTargetPlugins'] = plugins; |
| return true; |
| } |
| return false; |
| }; |
| |
| /** |
| * Checks whether the two URLs are from the same origin. |
| */ |
| __gCrWeb.common.isSameOrigin = function(url_one, url_two) { |
| return new URL(url_one).origin == new URL(url_two).origin; |
| } |
| |
| }()); // End of anonymous object |