1 // Copyright (c) 2012 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 <include src
="assert.js">
15 * Alias for document.getElementById.
16 * @param {string} id The ID of the element to find.
17 * @return {HTMLElement} The found element or null if not found.
20 return document
.getElementById(id
);
24 * Add an accessible message to the page that will be announced to
25 * users who have spoken feedback on, but will be invisible to all
26 * other users. It's removed right away so it doesn't clutter the DOM.
27 * @param {string} msg The text to be pronounced.
29 function announceAccessibleMessage(msg
) {
30 var element
= document
.createElement('div');
31 element
.setAttribute('aria-live', 'polite');
32 element
.style
.position
= 'relative';
33 element
.style
.left
= '-9999px';
34 element
.style
.height
= '0px';
35 element
.innerText
= msg
;
36 document
.body
.appendChild(element
);
37 window
.setTimeout(function() {
38 document
.body
.removeChild(element
);
43 * Calls chrome.send with a callback and restores the original afterwards.
44 * @param {string} name The name of the message to send.
45 * @param {!Array} params The parameters to send.
46 * @param {string} callbackName The name of the function that the backend calls.
47 * @param {!Function} callback The function to call.
49 function chromeSend(name
, params
, callbackName
, callback
) {
50 var old
= global
[callbackName
];
51 global
[callbackName
] = function() {
53 global
[callbackName
] = old
;
55 var args
= Array
.prototype.slice
.call(arguments
);
56 return callback
.apply(global
, args
);
58 chrome
.send(name
, params
);
62 * Returns the scale factors supported by this platform.
63 * @return {array} The supported scale factors.
65 function getSupportedScaleFactors() {
66 var supportedScaleFactors
= [];
67 if (cr
.isMac
|| cr
.isChromeOS
) {
68 supportedScaleFactors
.push(1);
69 supportedScaleFactors
.push(2);
71 // Windows must be restarted to display at a different scale factor.
72 supportedScaleFactors
.push(window
.devicePixelRatio
);
74 return supportedScaleFactors
;
78 * Generates a CSS url string.
79 * @param {string} s The URL to generate the CSS url for.
80 * @return {string} The CSS url string.
83 // http://www.w3.org/TR/css3-values/#uris
84 // Parentheses, commas, whitespace characters, single quotes (') and double
85 // quotes (") appearing in a URI must be escaped with a backslash
86 var s2
= s
.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
87 // WebKit has a bug when it comes to URLs that end with \
88 // https://bugs.webkit.org/show_bug.cgi?id=28885
89 if (/\\\\$/.test(s2
)) {
90 // Add a space to work around the WebKit bug.
93 return 'url("' + s2
+ '")';
97 * Returns the URL of the image, or an image set of URLs for the profile avatar.
98 * Default avatars have resources available for multiple scalefactors, whereas
99 * the GAIA profile image only comes in one size.
101 * @param {string} url The path of the image.
102 * @return {string} The url, or an image set of URLs of the avatar image.
104 function getProfileAvatarIcon(path
) {
105 var chromeThemePath
= 'chrome://theme';
106 var isDefaultAvatar
=
107 (path
.slice(0, chromeThemePath
.length
) == chromeThemePath
);
108 return isDefaultAvatar
? imageset(path
+ '@scalefactorx'): url(path
);
112 * Generates a CSS -webkit-image-set for a chrome:// url.
113 * An entry in the image set is added for each of getSupportedScaleFactors().
114 * The scale-factor-specific url is generated by replacing the first instance of
115 * 'scalefactor' in |path| with the numeric scale factor.
116 * @param {string} path The URL to generate an image set for.
117 * 'scalefactor' should be a substring of |path|.
118 * @return {string} The CSS -webkit-image-set.
120 function imageset(path
) {
121 var supportedScaleFactors
= getSupportedScaleFactors();
123 var replaceStartIndex
= path
.indexOf('scalefactor');
124 if (replaceStartIndex
< 0)
128 for (var i
= 0; i
< supportedScaleFactors
.length
; ++i
) {
129 var scaleFactor
= supportedScaleFactors
[i
];
130 var pathWithScaleFactor
= path
.substr(0, replaceStartIndex
) + scaleFactor
+
131 path
.substr(replaceStartIndex
+ 'scalefactor'.length
);
133 s
+= url(pathWithScaleFactor
) + ' ' + scaleFactor
+ 'x';
135 if (i
!= supportedScaleFactors
.length
- 1)
138 return '-webkit-image-set(' + s
+ ')';
142 * Parses query parameters from Location.
143 * @param {string} location The URL to generate the CSS url for.
144 * @return {object} Dictionary containing name value pairs for URL
146 function parseQueryParams(location
) {
148 var query
= unescape(location
.search
.substring(1));
149 var vars
= query
.split('&');
150 for (var i
= 0; i
< vars
.length
; i
++) {
151 var pair
= vars
[i
].split('=');
152 params
[pair
[0]] = pair
[1];
158 * Creates a new URL by appending or replacing the given query key and value.
159 * Not supporting URL with username and password.
160 * @param {object} location The original URL.
161 * @param {string} key The query parameter name.
162 * @param {string} value The query parameter value.
163 * @return {string} The constructed new URL.
165 function setQueryParam(location
, key
, value
) {
166 var query
= parseQueryParams(location
);
167 query
[encodeURIComponent(key
)] = encodeURIComponent(value
);
170 for (var q
in query
) {
171 newQuery
+= (newQuery
? '&' : '?') + q
+ '=' + query
[q
];
174 return location
.origin
+ location
.pathname
+ newQuery
+ location
.hash
;
177 function findAncestorByClass(el
, className
) {
178 return findAncestor(el
, function(el
) {
180 return el
.classList
.contains(className
);
186 * Return the first ancestor for which the {@code predicate} returns true.
187 * @param {Node} node The node to check.
188 * @param {function(Node) : boolean} predicate The function that tests the
190 * @return {Node} The found ancestor or null if not found.
192 function findAncestor(node
, predicate
) {
194 while (node
!= null && !(last
= predicate(node
))) {
195 node
= node
.parentNode
;
197 return last
? node
: null;
200 function swapDomNodes(a
, b
) {
201 var afterA
= a
.nextSibling
;
206 var aParent
= a
.parentNode
;
207 b
.parentNode
.replaceChild(a
, b
);
208 aParent
.insertBefore(b
, afterA
);
212 * Disables text selection and dragging, with optional whitelist callbacks.
213 * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
214 * is defined and returns true, the onselectionstart event will be
216 * @param {function(Event):boolean=} opt_allowDragStart Unless this function
217 * is defined and returns true, the ondragstart event will be surpressed.
219 function disableTextSelectAndDrag(opt_allowSelectStart
, opt_allowDragStart
) {
220 // Disable text selection.
221 document
.onselectstart = function(e
) {
222 if (!(opt_allowSelectStart
&& opt_allowSelectStart
.call(this, e
)))
227 document
.ondragstart = function(e
) {
228 if (!(opt_allowDragStart
&& opt_allowDragStart
.call(this, e
)))
234 * Call this to stop clicks on <a href="#"> links from scrolling to the top of
235 * the page (and possibly showing a # in the link).
237 function preventDefaultOnPoundLinkClicks() {
238 document
.addEventListener('click', function(e
) {
239 var anchor
= findAncestor(e
.target
, function(el
) {
240 return el
.tagName
== 'A';
242 // Use getAttribute() to prevent URL normalization.
243 if (anchor
&& anchor
.getAttribute('href') == '#')
249 * Check the directionality of the page.
250 * @return {boolean} True if Chrome is running an RTL UI.
253 return document
.documentElement
.dir
== 'rtl';
257 * Get an element that's known to exist by its ID. We use this instead of just
258 * calling getElementById and not checking the result because this lets us
259 * satisfy the JSCompiler type system.
260 * @param {string} id The identifier name.
261 * @return {!Element} the Element.
263 function getRequiredElement(id
) {
265 assert(element
, 'Missing required element: ' + id
);
269 // Handle click on a link. If the link points to a chrome: or file: url, then
270 // call into the browser to do the navigation.
271 document
.addEventListener('click', function(e
) {
272 if (e
.defaultPrevented
)
276 if (el
.nodeType
== Node
.ELEMENT_NODE
&&
277 el
.webkitMatchesSelector('A, A *')) {
278 while (el
.tagName
!= 'A') {
279 el
= el
.parentElement
;
282 if ((el
.protocol
== 'file:' || el
.protocol
== 'about:') &&
283 (e
.button
== 0 || e
.button
== 1)) {
284 chrome
.send('navigateToUrl', [
299 * Creates a new URL which is the old URL with a GET param of key=value.
300 * @param {string} url The base URL. There is not sanity checking on the URL so
301 * it must be passed in a proper format.
302 * @param {string} key The key of the param.
303 * @param {string} value The value of the param.
304 * @return {string} The new URL.
306 function appendParam(url
, key
, value
) {
307 var param
= encodeURIComponent(key
) + '=' + encodeURIComponent(value
);
309 if (url
.indexOf('?') == -1)
310 return url
+ '?' + param
;
311 return url
+ '&' + param
;
315 * Creates a CSS -webkit-image-set for a favicon request.
316 * @param {string} url The url for the favicon.
317 * @param {number=} opt_size Optional preferred size of the favicon.
318 * @param {string=} opt_type Optional type of favicon to request. Valid values
319 * are 'favicon' and 'touch-icon'. Default is 'favicon'.
320 * @return {string} -webkit-image-set for the favicon.
322 function getFaviconImageSet(url
, opt_size
, opt_type
) {
323 var size
= opt_size
|| 16;
324 var type
= opt_type
|| 'favicon';
326 'chrome://' + type
+ '/size/' + size
+ '@scalefactorx/' + url
);
330 * Creates a new URL for a favicon request for the current device pixel ratio.
331 * The URL must be updated when the user moves the browser to a screen with a
332 * different device pixel ratio. Use getFaviconImageSet() for the updating to
333 * occur automatically.
334 * @param {string} url The url for the favicon.
335 * @param {number=} opt_size Optional preferred size of the favicon.
336 * @param {string=} opt_type Optional type of favicon to request. Valid values
337 * are 'favicon' and 'touch-icon'. Default is 'favicon'.
338 * @return {string} Updated URL for the favicon.
340 function getFaviconUrlForCurrentDevicePixelRatio(url
, opt_size
, opt_type
) {
341 var size
= opt_size
|| 16;
342 var type
= opt_type
|| 'favicon';
343 return 'chrome://' + type
+ '/size/' + size
+ '@' +
344 window
.devicePixelRatio
+ 'x/' + url
;
348 * Creates an element of a specified type with a specified class name.
349 * @param {string} type The node type.
350 * @param {string} className The class name to use.
351 * @return {Element} The created element.
353 function createElementWithClassName(type
, className
) {
354 var elm
= document
.createElement(type
);
355 elm
.className
= className
;
360 * webkitTransitionEnd does not always fire (e.g. when animation is aborted
361 * or when no paint happens during the animation). This function sets up
362 * a timer and emulate the event if it is not fired when the timer expires.
363 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd.
364 * @param {number} timeOut The maximum wait time in milliseconds for the
365 * webkitTransitionEnd to happen.
367 function ensureTransitionEndEvent(el
, timeOut
) {
369 el
.addEventListener('webkitTransitionEnd', function f(e
) {
370 el
.removeEventListener('webkitTransitionEnd', f
);
373 window
.setTimeout(function() {
375 cr
.dispatchSimpleEvent(el
, 'webkitTransitionEnd');
380 * Alias for document.scrollTop getter.
381 * @param {!HTMLDocument} doc The document node where information will be
383 * @return {number} The Y document scroll offset.
385 function scrollTopForDocument(doc
) {
386 return doc
.documentElement
.scrollTop
|| doc
.body
.scrollTop
;
390 * Alias for document.scrollTop setter.
391 * @param {!HTMLDocument} doc The document node where information will be
393 * @param {number} value The target Y scroll offset.
395 function setScrollTopForDocument(doc
, value
) {
396 doc
.documentElement
.scrollTop
= doc
.body
.scrollTop
= value
;
400 * Alias for document.scrollLeft getter.
401 * @param {!HTMLDocument} doc The document node where information will be
403 * @return {number} The X document scroll offset.
405 function scrollLeftForDocument(doc
) {
406 return doc
.documentElement
.scrollLeft
|| doc
.body
.scrollLeft
;
410 * Alias for document.scrollLeft setter.
411 * @param {!HTMLDocument} doc The document node where information will be
413 * @param {number} value The target X scroll offset.
415 function setScrollLeftForDocument(doc
, value
) {
416 doc
.documentElement
.scrollLeft
= doc
.body
.scrollLeft
= value
;
420 * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding.
421 * @param {string} original The original string.
422 * @return {string} The string with all the characters mentioned above replaced.
424 function HTMLEscape(original
) {
425 return original
.replace(/&/g
, '&')
426 .replace(/</g
, '<')
427 .replace(/>/g
, '>')
428 .replace(/"/g, '"')
429 .replace(/'/g, ''');
433 * Shortens the provided string (if necessary) to a string of length at most
435 * @param {string} original The original string.
436 * @param {number} maxLength The maximum length allowed for the string.
437 * @return {string} The original string if its length does not exceed
438 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...'
441 function elide(original, maxLength) {
442 if (original.length <= maxLength)
444 return original.substring(0, maxLength - 1) + '\u2026';