• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4
5<include src="assert.js">
6
7/**
8 * The global object.
9 * @type {!Object}
10 * @const
11 */
12var global = this;
13
14/**
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.
18 */
19function $(id) {
20  return document.getElementById(id);
21}
22
23/**
24 * Calls chrome.send with a callback and restores the original afterwards.
25 * @param {string} name The name of the message to send.
26 * @param {!Array} params The parameters to send.
27 * @param {string} callbackName The name of the function that the backend calls.
28 * @param {!Function} callback The function to call.
29 */
30function chromeSend(name, params, callbackName, callback) {
31  var old = global[callbackName];
32  global[callbackName] = function() {
33    // restore
34    global[callbackName] = old;
35
36    var args = Array.prototype.slice.call(arguments);
37    return callback.apply(global, args);
38  };
39  chrome.send(name, params);
40}
41
42/**
43 * Returns the scale factors supported by this platform.
44 * @return {array} The supported scale factors.
45 */
46function getSupportedScaleFactors() {
47  var supportedScaleFactors = [];
48  if (cr.isMac || cr.isChromeOS) {
49    supportedScaleFactors.push(1);
50    supportedScaleFactors.push(2);
51  } else {
52    // Windows must be restarted to display at a different scale factor.
53    supportedScaleFactors.push(window.devicePixelRatio);
54  }
55  return supportedScaleFactors;
56}
57
58/**
59 * Generates a CSS url string.
60 * @param {string} s The URL to generate the CSS url for.
61 * @return {string} The CSS url string.
62 */
63function url(s) {
64  // http://www.w3.org/TR/css3-values/#uris
65  // Parentheses, commas, whitespace characters, single quotes (') and double
66  // quotes (") appearing in a URI must be escaped with a backslash
67  var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
68  // WebKit has a bug when it comes to URLs that end with \
69  // https://bugs.webkit.org/show_bug.cgi?id=28885
70  if (/\\\\$/.test(s2)) {
71    // Add a space to work around the WebKit bug.
72    s2 += ' ';
73  }
74  return 'url("' + s2 + '")';
75}
76
77/**
78 * Generates a CSS -webkit-image-set for a chrome:// url.
79 * An entry in the image set is added for each of getSupportedScaleFactors().
80 * The scale-factor-specific url is generated by replacing the first instance of
81 * 'scalefactor' in |path| with the numeric scale factor.
82 * @param {string} path The URL to generate an image set for.
83 *     'scalefactor' should be a substring of |path|.
84 * @return {string} The CSS -webkit-image-set.
85 */
86function imageset(path) {
87  var supportedScaleFactors = getSupportedScaleFactors();
88
89  var replaceStartIndex = path.indexOf('scalefactor');
90  if (replaceStartIndex < 0)
91    return url(path);
92
93  var s = '';
94  for (var i = 0; i < supportedScaleFactors.length; ++i) {
95    var scaleFactor = supportedScaleFactors[i];
96    var pathWithScaleFactor = path.substr(0, replaceStartIndex) + scaleFactor +
97        path.substr(replaceStartIndex + 'scalefactor'.length);
98
99    s += url(pathWithScaleFactor) + ' ' + scaleFactor + 'x';
100
101    if (i != supportedScaleFactors.length - 1)
102      s += ', ';
103  }
104  return '-webkit-image-set(' + s + ')';
105}
106
107/**
108 * Parses query parameters from Location.
109 * @param {string} location The URL to generate the CSS url for.
110 * @return {object} Dictionary containing name value pairs for URL
111 */
112function parseQueryParams(location) {
113  var params = {};
114  var query = unescape(location.search.substring(1));
115  var vars = query.split('&');
116  for (var i = 0; i < vars.length; i++) {
117    var pair = vars[i].split('=');
118    params[pair[0]] = pair[1];
119  }
120  return params;
121}
122
123/**
124 * Creates a new URL by appending or replacing the given query key and value.
125 * Not supporting URL with username and password.
126 * @param {object} location The original URL.
127 * @param {string} key The query parameter name.
128 * @param {string} value The query parameter value.
129 * @return {string} The constructed new URL.
130 */
131function setQueryParam(location, key, value) {
132  var query = parseQueryParams(location);
133  query[encodeURIComponent(key)] = encodeURIComponent(value);
134
135  var newQuery = '';
136  for (var q in query) {
137    newQuery += (newQuery ? '&' : '?') + q + '=' + query[q];
138  }
139
140  return location.origin + location.pathname + newQuery + location.hash;
141}
142
143function findAncestorByClass(el, className) {
144  return findAncestor(el, function(el) {
145    if (el.classList)
146      return el.classList.contains(className);
147    return null;
148  });
149}
150
151/**
152 * Return the first ancestor for which the {@code predicate} returns true.
153 * @param {Node} node The node to check.
154 * @param {function(Node) : boolean} predicate The function that tests the
155 *     nodes.
156 * @return {Node} The found ancestor or null if not found.
157 */
158function findAncestor(node, predicate) {
159  var last = false;
160  while (node != null && !(last = predicate(node))) {
161    node = node.parentNode;
162  }
163  return last ? node : null;
164}
165
166function swapDomNodes(a, b) {
167  var afterA = a.nextSibling;
168  if (afterA == b) {
169    swapDomNodes(b, a);
170    return;
171  }
172  var aParent = a.parentNode;
173  b.parentNode.replaceChild(a, b);
174  aParent.insertBefore(b, afterA);
175}
176
177/**
178 * Disables text selection and dragging, with optional whitelist callbacks.
179 * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
180 *    is defined and returns true, the onselectionstart event will be
181 *    surpressed.
182 * @param {function(Event):boolean=} opt_allowDragStart Unless this function
183 *    is defined and returns true, the ondragstart event will be surpressed.
184 */
185function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
186  // Disable text selection.
187  document.onselectstart = function(e) {
188    if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
189      e.preventDefault();
190  };
191
192  // Disable dragging.
193  document.ondragstart = function(e) {
194    if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
195      e.preventDefault();
196  };
197}
198
199/**
200 * Call this to stop clicks on <a href="#"> links from scrolling to the top of
201 * the page (and possibly showing a # in the link).
202 */
203function preventDefaultOnPoundLinkClicks() {
204  document.addEventListener('click', function(e) {
205    var anchor = findAncestor(e.target, function(el) {
206      return el.tagName == 'A';
207    });
208    // Use getAttribute() to prevent URL normalization.
209    if (anchor && anchor.getAttribute('href') == '#')
210      e.preventDefault();
211  });
212}
213
214/**
215 * Check the directionality of the page.
216 * @return {boolean} True if Chrome is running an RTL UI.
217 */
218function isRTL() {
219  return document.documentElement.dir == 'rtl';
220}
221
222/**
223 * Get an element that's known to exist by its ID. We use this instead of just
224 * calling getElementById and not checking the result because this lets us
225 * satisfy the JSCompiler type system.
226 * @param {string} id The identifier name.
227 * @return {!Element} the Element.
228 */
229function getRequiredElement(id) {
230  var element = $(id);
231  assert(element, 'Missing required element: ' + id);
232  return element;
233}
234
235// Handle click on a link. If the link points to a chrome: or file: url, then
236// call into the browser to do the navigation.
237document.addEventListener('click', function(e) {
238  if (e.defaultPrevented)
239    return;
240
241  var el = e.target;
242  if (el.nodeType == Node.ELEMENT_NODE &&
243      el.webkitMatchesSelector('A, A *')) {
244    while (el.tagName != 'A') {
245      el = el.parentElement;
246    }
247
248    if ((el.protocol == 'file:' || el.protocol == 'about:') &&
249        (e.button == 0 || e.button == 1)) {
250      chrome.send('navigateToUrl', [
251        el.href,
252        el.target,
253        e.button,
254        e.altKey,
255        e.ctrlKey,
256        e.metaKey,
257        e.shiftKey
258      ]);
259      e.preventDefault();
260    }
261  }
262});
263
264/**
265 * Creates a new URL which is the old URL with a GET param of key=value.
266 * @param {string} url The base URL. There is not sanity checking on the URL so
267 *     it must be passed in a proper format.
268 * @param {string} key The key of the param.
269 * @param {string} value The value of the param.
270 * @return {string} The new URL.
271 */
272function appendParam(url, key, value) {
273  var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
274
275  if (url.indexOf('?') == -1)
276    return url + '?' + param;
277  return url + '&' + param;
278}
279
280/**
281 * Creates a CSS -webkit-image-set for a favicon request.
282 * @param {string} url The url for the favicon.
283 * @param {number=} opt_size Optional preferred size of the favicon.
284 * @param {string=} opt_type Optional type of favicon to request. Valid values
285 *     are 'favicon' and 'touch-icon'. Default is 'favicon'.
286 * @return {string} -webkit-image-set for the favicon.
287 */
288function getFaviconImageSet(url, opt_size, opt_type) {
289  var size = opt_size || 16;
290  var type = opt_type || 'favicon';
291  return imageset(
292      'chrome://' + type + '/size/' + size + '@scalefactorx/' + url);
293}
294
295/**
296 * Creates a new URL for a favicon request for the current device pixel ratio.
297 * The URL must be updated when the user moves the browser to a screen with a
298 * different device pixel ratio. Use getFaviconImageSet() for the updating to
299 * occur automatically.
300 * @param {string} url The url for the favicon.
301 * @param {number=} opt_size Optional preferred size of the favicon.
302 * @param {string=} opt_type Optional type of favicon to request. Valid values
303 *     are 'favicon' and 'touch-icon'. Default is 'favicon'.
304 * @return {string} Updated URL for the favicon.
305 */
306function getFaviconUrlForCurrentDevicePixelRatio(url, opt_size, opt_type) {
307  var size = opt_size || 16;
308  var type = opt_type || 'favicon';
309  return 'chrome://' + type + '/size/' + size + '@' +
310      window.devicePixelRatio + 'x/' + url;
311}
312
313/**
314 * Creates an element of a specified type with a specified class name.
315 * @param {string} type The node type.
316 * @param {string} className The class name to use.
317 * @return {Element} The created element.
318 */
319function createElementWithClassName(type, className) {
320  var elm = document.createElement(type);
321  elm.className = className;
322  return elm;
323}
324
325/**
326 * webkitTransitionEnd does not always fire (e.g. when animation is aborted
327 * or when no paint happens during the animation). This function sets up
328 * a timer and emulate the event if it is not fired when the timer expires.
329 * @param {!HTMLElement} el The element to watch for webkitTransitionEnd.
330 * @param {number} timeOut The maximum wait time in milliseconds for the
331 *     webkitTransitionEnd to happen.
332 */
333function ensureTransitionEndEvent(el, timeOut) {
334  var fired = false;
335  el.addEventListener('webkitTransitionEnd', function f(e) {
336    el.removeEventListener('webkitTransitionEnd', f);
337    fired = true;
338  });
339  window.setTimeout(function() {
340    if (!fired)
341      cr.dispatchSimpleEvent(el, 'webkitTransitionEnd');
342  }, timeOut);
343}
344
345/**
346 * Alias for document.scrollTop getter.
347 * @param {!HTMLDocument} doc The document node where information will be
348 *     queried from.
349 * @return {number} The Y document scroll offset.
350 */
351function scrollTopForDocument(doc) {
352  return doc.documentElement.scrollTop || doc.body.scrollTop;
353}
354
355/**
356 * Alias for document.scrollTop setter.
357 * @param {!HTMLDocument} doc The document node where information will be
358 *     queried from.
359 * @param {number} value The target Y scroll offset.
360 */
361function setScrollTopForDocument(doc, value) {
362  doc.documentElement.scrollTop = doc.body.scrollTop = value;
363}
364
365/**
366 * Alias for document.scrollLeft getter.
367 * @param {!HTMLDocument} doc The document node where information will be
368 *     queried from.
369 * @return {number} The X document scroll offset.
370 */
371function scrollLeftForDocument(doc) {
372  return doc.documentElement.scrollLeft || doc.body.scrollLeft;
373}
374
375/**
376 * Alias for document.scrollLeft setter.
377 * @param {!HTMLDocument} doc The document node where information will be
378 *     queried from.
379 * @param {number} value The target X scroll offset.
380 */
381function setScrollLeftForDocument(doc, value) {
382  doc.documentElement.scrollLeft = doc.body.scrollLeft = value;
383}
384