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">
8 * Alias for document.getElementById.
9 * @param {string} id The ID of the element to find.
10 * @return {HTMLElement} The found element or null if not found.
13 return document
.getElementById(id
);
17 * Add an accessible message to the page that will be announced to
18 * users who have spoken feedback on, but will be invisible to all
19 * other users. It's removed right away so it doesn't clutter the DOM.
20 * @param {string} msg The text to be pronounced.
22 function announceAccessibleMessage(msg
) {
23 var element
= document
.createElement('div');
24 element
.setAttribute('aria-live', 'polite');
25 element
.style
.position
= 'relative';
26 element
.style
.left
= '-9999px';
27 element
.style
.height
= '0px';
28 element
.innerText
= msg
;
29 document
.body
.appendChild(element
);
30 window
.setTimeout(function() {
31 document
.body
.removeChild(element
);
36 * Calls chrome.send with a callback and restores the original afterwards.
37 * @param {string} name The name of the message to send.
38 * @param {!Array} params The parameters to send.
39 * @param {string} callbackName The name of the function that the backend calls.
40 * @param {!Function} callback The function to call.
42 function chromeSend(name
, params
, callbackName
, callback
) {
43 var old
= global
[callbackName
];
44 global
[callbackName
] = function() {
46 global
[callbackName
] = old
;
48 var args
= Array
.prototype.slice
.call(arguments
);
49 return callback
.apply(global
, args
);
51 chrome
.send(name
, params
);
55 * Returns the scale factors supported by this platform.
56 * @return {Array} The supported scale factors.
58 function getSupportedScaleFactors() {
59 var supportedScaleFactors
= [];
60 if (cr
.isMac
|| cr
.isChromeOS
) {
61 supportedScaleFactors
.push(1);
62 supportedScaleFactors
.push(2);
64 // Windows must be restarted to display at a different scale factor.
65 supportedScaleFactors
.push(window
.devicePixelRatio
);
67 return supportedScaleFactors
;
71 * Generates a CSS url string.
72 * @param {string} s The URL to generate the CSS url for.
73 * @return {string} The CSS url string.
76 // http://www.w3.org/TR/css3-values/#uris
77 // Parentheses, commas, whitespace characters, single quotes (') and double
78 // quotes (") appearing in a URI must be escaped with a backslash
79 var s2
= s
.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
80 // WebKit has a bug when it comes to URLs that end with \
81 // https://bugs.webkit.org/show_bug.cgi?id=28885
82 if (/\\\\$/.test(s2
)) {
83 // Add a space to work around the WebKit bug.
86 return 'url("' + s2
+ '")';
90 * Returns the URL of the image, or an image set of URLs for the profile avatar.
91 * Default avatars have resources available for multiple scalefactors, whereas
92 * the GAIA profile image only comes in one size.
94 * @param {string} path The path of the image.
95 * @return {string} The url, or an image set of URLs of the avatar image.
97 function getProfileAvatarIcon(path
) {
98 var chromeThemePath
= 'chrome://theme';
100 (path
.slice(0, chromeThemePath
.length
) == chromeThemePath
);
101 return isDefaultAvatar
? imageset(path
+ '@scalefactorx'): url(path
);
105 * Generates a CSS -webkit-image-set for a chrome:// url.
106 * An entry in the image set is added for each of getSupportedScaleFactors().
107 * The scale-factor-specific url is generated by replacing the first instance of
108 * 'scalefactor' in |path| with the numeric scale factor.
109 * @param {string} path The URL to generate an image set for.
110 * 'scalefactor' should be a substring of |path|.
111 * @return {string} The CSS -webkit-image-set.
113 function imageset(path
) {
114 var supportedScaleFactors
= getSupportedScaleFactors();
116 var replaceStartIndex
= path
.indexOf('scalefactor');
117 if (replaceStartIndex
< 0)
121 for (var i
= 0; i
< supportedScaleFactors
.length
; ++i
) {
122 var scaleFactor
= supportedScaleFactors
[i
];
123 var pathWithScaleFactor
= path
.substr(0, replaceStartIndex
) + scaleFactor
+
124 path
.substr(replaceStartIndex
+ 'scalefactor'.length
);
126 s
+= url(pathWithScaleFactor
) + ' ' + scaleFactor
+ 'x';
128 if (i
!= supportedScaleFactors
.length
- 1)
131 return '-webkit-image-set(' + s
+ ')';
135 * Parses query parameters from Location.
136 * @param {Location} location The URL to generate the CSS url for.
137 * @return {Object} Dictionary containing name value pairs for URL
139 function parseQueryParams(location
) {
141 var query
= unescape(location
.search
.substring(1));
142 var vars
= query
.split('&');
143 for (var i
= 0; i
< vars
.length
; i
++) {
144 var pair
= vars
[i
].split('=');
145 params
[pair
[0]] = pair
[1];
151 * Creates a new URL by appending or replacing the given query key and value.
152 * Not supporting URL with username and password.
153 * @param {Location} location The original URL.
154 * @param {string} key The query parameter name.
155 * @param {string} value The query parameter value.
156 * @return {string} The constructed new URL.
158 function setQueryParam(location
, key
, value
) {
159 var query
= parseQueryParams(location
);
160 query
[encodeURIComponent(key
)] = encodeURIComponent(value
);
163 for (var q
in query
) {
164 newQuery
+= (newQuery
? '&' : '?') + q
+ '=' + query
[q
];
167 return location
.origin
+ location
.pathname
+ newQuery
+ location
.hash
;
171 * @param {Node} el A node to search for ancestors with |className|.
172 * @param {string} className A class to search for.
173 * @return {Element} A node with class of |className| or null if none is found.
175 function findAncestorByClass(el
, className
) {
176 return /** @type {Element} */(findAncestor(el
, function(el
) {
177 return el
.classList
&& el
.classList
.contains(className
);
182 * Return the first ancestor for which the {@code predicate} returns true.
183 * @param {Node} node The node to check.
184 * @param {function(Node):boolean} predicate The function that tests the
186 * @return {Node} The found ancestor or null if not found.
188 function findAncestor(node
, predicate
) {
190 while (node
!= null && !(last
= predicate(node
))) {
191 node
= node
.parentNode
;
193 return last
? node
: null;
196 function swapDomNodes(a
, b
) {
197 var afterA
= a
.nextSibling
;
202 var aParent
= a
.parentNode
;
203 b
.parentNode
.replaceChild(a
, b
);
204 aParent
.insertBefore(b
, afterA
);
208 * Disables text selection and dragging, with optional whitelist callbacks.
209 * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
210 * is defined and returns true, the onselectionstart event will be
212 * @param {function(Event):boolean=} opt_allowDragStart Unless this function
213 * is defined and returns true, the ondragstart event will be surpressed.
215 function disableTextSelectAndDrag(opt_allowSelectStart
, opt_allowDragStart
) {
216 // Disable text selection.
217 document
.onselectstart = function(e
) {
218 if (!(opt_allowSelectStart
&& opt_allowSelectStart
.call(this, e
)))
223 document
.ondragstart = function(e
) {
224 if (!(opt_allowDragStart
&& opt_allowDragStart
.call(this, e
)))
230 * TODO(dbeam): DO NOT USE. THIS IS DEPRECATED. Use an action-link instead.
231 * Call this to stop clicks on <a href="#"> links from scrolling to the top of
232 * the page (and possibly showing a # in the link).
234 function preventDefaultOnPoundLinkClicks() {
235 document
.addEventListener('click', function(e
) {
236 var anchor
= findAncestor(/** @type {Node} */(e
.target
), function(el
) {
237 return el
.tagName
== 'A';
239 // Use getAttribute() to prevent URL normalization.
240 if (anchor
&& anchor
.getAttribute('href') == '#')
246 * Check the directionality of the page.
247 * @return {boolean} True if Chrome is running an RTL UI.
250 return document
.documentElement
.dir
== 'rtl';
254 * Get an element that's known to exist by its ID. We use this instead of just
255 * calling getElementById and not checking the result because this lets us
256 * satisfy the JSCompiler type system.
257 * @param {string} id The identifier name.
258 * @return {!HTMLElement} the Element.
260 function getRequiredElement(id
) {
261 return assertInstanceof($(id
), HTMLElement
,
262 'Missing required element: ' + id
);
266 * Query an element that's known to exist by a selector. We use this instead of
267 * just calling querySelector and not checking the result because this lets us
268 * satisfy the JSCompiler type system.
269 * @param {(!Document|!DocumentFragment|!Element)} context The context object
271 * @param {string} selectors CSS selectors to query the element.
272 * @return {!HTMLElement} the Element.
274 function queryRequiredElement(context
, selectors
) {
275 var element
= context
.querySelector(selectors
);
276 return assertInstanceof(element
, HTMLElement
,
277 'Missing required element: ' + selectors
);
280 // Handle click on a link. If the link points to a chrome: or file: url, then
281 // call into the browser to do the navigation.
282 document
.addEventListener('click', function(e
) {
283 if (e
.defaultPrevented
)
287 if (el
.nodeType
== Node
.ELEMENT_NODE
&&
288 el
.webkitMatchesSelector('A, A *')) {
289 while (el
.tagName
!= 'A') {
290 el
= el
.parentElement
;
293 if ((el
.protocol
== 'file:' || el
.protocol
== 'about:') &&
294 (e
.button
== 0 || e
.button
== 1)) {
295 chrome
.send('navigateToUrl', [
310 * Creates a new URL which is the old URL with a GET param of key=value.
311 * @param {string} url The base URL. There is not sanity checking on the URL so
312 * it must be passed in a proper format.
313 * @param {string} key The key of the param.
314 * @param {string} value The value of the param.
315 * @return {string} The new URL.
317 function appendParam(url
, key
, value
) {
318 var param
= encodeURIComponent(key
) + '=' + encodeURIComponent(value
);
320 if (url
.indexOf('?') == -1)
321 return url
+ '?' + param
;
322 return url
+ '&' + param
;
326 * Creates a CSS -webkit-image-set for a favicon request.
327 * @param {string} url The url for the favicon.
328 * @param {number=} opt_size Optional preferred size of the favicon.
329 * @param {string=} opt_type Optional type of favicon to request. Valid values
330 * are 'favicon' and 'touch-icon'. Default is 'favicon'.
331 * @return {string} -webkit-image-set for the favicon.
333 function getFaviconImageSet(url
, opt_size
, opt_type
) {
334 var size
= opt_size
|| 16;
335 var type
= opt_type
|| 'favicon';
337 'chrome://' + type
+ '/size/' + size
+ '@scalefactorx/' + url
);
341 * Creates a new URL for a favicon request for the current device pixel ratio.
342 * The URL must be updated when the user moves the browser to a screen with a
343 * different device pixel ratio. Use getFaviconImageSet() for the updating to
344 * occur automatically.
345 * @param {string} url The url for the favicon.
346 * @param {number=} opt_size Optional preferred size of the favicon.
347 * @param {string=} opt_type Optional type of favicon to request. Valid values
348 * are 'favicon' and 'touch-icon'. Default is 'favicon'.
349 * @return {string} Updated URL for the favicon.
351 function getFaviconUrlForCurrentDevicePixelRatio(url
, opt_size
, opt_type
) {
352 var size
= opt_size
|| 16;
353 var type
= opt_type
|| 'favicon';
354 return 'chrome://' + type
+ '/size/' + size
+ '@' +
355 window
.devicePixelRatio
+ 'x/' + url
;
359 * Creates an element of a specified type with a specified class name.
360 * @param {string} type The node type.
361 * @param {string} className The class name to use.
362 * @return {Element} The created element.
364 function createElementWithClassName(type
, className
) {
365 var elm
= document
.createElement(type
);
366 elm
.className
= className
;
371 * webkitTransitionEnd does not always fire (e.g. when animation is aborted
372 * or when no paint happens during the animation). This function sets up
373 * a timer and emulate the event if it is not fired when the timer expires.
374 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd.
375 * @param {number} timeOut The maximum wait time in milliseconds for the
376 * webkitTransitionEnd to happen.
378 function ensureTransitionEndEvent(el
, timeOut
) {
380 el
.addEventListener('webkitTransitionEnd', function f(e
) {
381 el
.removeEventListener('webkitTransitionEnd', f
);
384 window
.setTimeout(function() {
386 cr
.dispatchSimpleEvent(el
, 'webkitTransitionEnd');
391 * Alias for document.scrollTop getter.
392 * @param {!HTMLDocument} doc The document node where information will be
394 * @return {number} The Y document scroll offset.
396 function scrollTopForDocument(doc
) {
397 return doc
.documentElement
.scrollTop
|| doc
.body
.scrollTop
;
401 * Alias for document.scrollTop setter.
402 * @param {!HTMLDocument} doc The document node where information will be
404 * @param {number} value The target Y scroll offset.
406 function setScrollTopForDocument(doc
, value
) {
407 doc
.documentElement
.scrollTop
= doc
.body
.scrollTop
= value
;
411 * Alias for document.scrollLeft getter.
412 * @param {!HTMLDocument} doc The document node where information will be
414 * @return {number} The X document scroll offset.
416 function scrollLeftForDocument(doc
) {
417 return doc
.documentElement
.scrollLeft
|| doc
.body
.scrollLeft
;
421 * Alias for document.scrollLeft setter.
422 * @param {!HTMLDocument} doc The document node where information will be
424 * @param {number} value The target X scroll offset.
426 function setScrollLeftForDocument(doc
, value
) {
427 doc
.documentElement
.scrollLeft
= doc
.body
.scrollLeft
= value
;
431 * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding.
432 * @param {string} original The original string.
433 * @return {string} The string with all the characters mentioned above replaced.
435 function HTMLEscape(original
) {
436 return original
.replace(/&/g
, '&')
437 .replace(/</g
, '<')
438 .replace(/>/g
, '>')
439 .replace(/"/g, '"')
440 .replace(/'/g, ''');
444 * Shortens the provided string (if necessary) to a string of length at most
446 * @param {string} original The original string.
447 * @param {number} maxLength The maximum length allowed for the string.
448 * @return {string} The original string if its length does not exceed
449 * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...'
452 function elide(original, maxLength) {
453 if (original.length <= maxLength)
455 return original.substring(0, maxLength - 1) + '\u2026';