Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / remoting / webapp / base / js / base.js
blobac6713209e52b5263001041048cc60cac3e77139
1 // Copyright 2014 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 /**
6  * @fileoverview
7  * A module that contains basic utility components and methods for the
8  * chromoting project
9  *
10  */
12 'use strict';
14 /** @suppress {duplicate} */
15 var base = base || {};
17 base.debug = function() {};
19 /**
20  * @return {string} The callstack of the current method.
21  */
22 base.debug.callstack = function() {
23   try {
24     throw new Error();
25   } catch (/** @type {Error} */ error) {
26     var callstack = error.stack
27       .replace(/^\s+(at eval )?at\s+/gm, '') // Remove 'at' and indentation.
28       .split('\n');
29     callstack.splice(0,2); // Remove the stack of the current function.
30   }
31   return callstack.join('\n');
34 /**
35   * @interface
36   */
37 base.Disposable = function() {};
38 base.Disposable.prototype.dispose = function() {};
40 /**
41  * @constructor
42  * @param {...base.Disposable} var_args
43  * @implements {base.Disposable}
44  * @suppress {reportUnknownTypes}
45  */
46 base.Disposables = function(var_args) {
47   /**
48    * @type {Array<base.Disposable>}
49    * @private
50    */
51   this.disposables_ = Array.prototype.slice.call(arguments, 0);
54 /**
55  * @param {...base.Disposable} var_args
56  * @suppress {reportUnknownTypes}
57  */
58 base.Disposables.prototype.add = function(var_args) {
59   var disposables = Array.prototype.slice.call(arguments, 0);
60   for (var i = 0; i < disposables.length; i++) {
61     var current = /** @type {base.Disposable} */ (disposables[i]);
62     if (this.disposables_.indexOf(current) === -1) {
63       this.disposables_.push(current);
64     }
65   }
68 /**
69  * @param {...base.Disposable} var_args  Dispose |var_args| and remove
70  *    them from the current object.
71  * @suppress {reportUnknownTypes}
72  */
73 base.Disposables.prototype.remove = function(var_args) {
74   var disposables = Array.prototype.slice.call(arguments, 0);
75   for (var i = 0; i < disposables.length; i++) {
76     var disposable = /** @type {base.Disposable} */ (disposables[i]);
77     var index = this.disposables_.indexOf(disposable);
78     if(index !== -1) {
79       this.disposables_.splice(index, 1);
80       disposable.dispose();
81     }
82   }
85 base.Disposables.prototype.dispose = function() {
86   for (var i = 0; i < this.disposables_.length; i++) {
87     this.disposables_[i].dispose();
88   }
89   this.disposables_ = null;
92 /**
93  * A utility function to invoke |obj|.dispose without a null check on |obj|.
94  * @param {base.Disposable} obj
95  */
96 base.dispose = function(obj) {
97   if (obj) {
98     console.assert(typeof obj.dispose == 'function',
99                    'dispose() should have type function, not ' +
100                    typeof obj.dispose + '.');
101     obj.dispose();
102   }
106  * Copy all properties from src to dest.
107  * @param {Object} dest
108  * @param {Object} src
109  */
110 base.mix = function(dest, src) {
111   for (var prop in src) {
112     if (src.hasOwnProperty(prop) && !(prop in dest)) {
113       dest[prop] = src[prop];
114     }
115   }
119  * Adds a mixin to a class.
120  * @param {Object} dest
121  * @param {Object} src
122  * @suppress {checkTypes|reportUnknownTypes}
123  */
124 base.extend = function(dest, src) {
125   base.mix(dest.prototype, src.prototype || src);
129  * Inherits properties and methods from |parentCtor| at object construction time
130  * using prototypical inheritance. e.g.
132  * var ParentClass = function(parentArg) {
133  *   this.parentProperty = parentArg;
134  * }
136  * var ChildClass = function() {
137  *   base.inherits(this, ParentClass, 'parentArg'); // must be the first line.
138  * }
140  * var child = new ChildClass();
141  * child instanceof ParentClass // true
143  * See base_inherits_unittest.js for the guaranteed behavior of base.inherits().
144  * This lazy approach is chosen so that it is not necessary to maintain proper
145  * script loading order between the parent class and the child class.
147  * @param {*} childObject
148  * @param {*} parentCtor
149  * @param {...} parentCtorArgs
150  * @suppress {checkTypes|reportUnknownTypes}
151  */
152 base.inherits = function(childObject, parentCtor, parentCtorArgs) {
153   console.assert(parentCtor && parentCtor.prototype,
154                  'Invalid parent constructor.');
155   var parentArgs = Array.prototype.slice.call(arguments, 2);
157   // Mix in the parent's prototypes so that they're available during the parent
158   // ctor.
159   base.mix(childObject, parentCtor.prototype);
160   parentCtor.apply(childObject, parentArgs);
162   // Note that __proto__ is deprecated.
163   //   https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
164   //   Global_Objects/Object/proto.
165   // It is used so that childObject instanceof parentCtor will
166   // return true.
167   childObject.__proto__.__proto__ = parentCtor.prototype;
168   console.assert(childObject instanceof parentCtor,
169                  'child is not an instance of parent.');
172 base.doNothing = function() {};
175  * Returns an array containing the values of |dict|.
176  * @param {!Object} dict
177  * @return {Array}
178  */
179 base.values = function(dict) {
180   return Object.keys(dict).map(
181     /** @param {string} key */
182     function(key) {
183       return dict[key];
184     });
188  * @param {*} value
189  * @return {*} a recursive copy of |value| or null if |value| is not copyable
190  *   (e.g. undefined, NaN).
191  */
192 base.deepCopy = function(value) {
193   try {
194     return JSON.parse(JSON.stringify(value));
195   } catch (e) {}
196   return null;
200  * Returns a copy of the input object with all null/undefined fields
201  * removed.  Returns an empty object for a null/undefined input.
203  * @param {Object<?T>|undefined} input
204  * @return {!Object<T>}
205  * @template T
206  */
207 base.copyWithoutNullFields = function(input) {
208   /** @const {!Object} */
209   var result = {};
210   if (input) {
211     for (var field in input) {
212       var value = /** @type {*} */ (input[field]);
213       if (value != null) {
214         result[field] = value;
215       }
216     }
217   }
218   return result;
222  * @param {!Object} object
223  * @return {boolean} True if the object is empty (equal to {}); false otherwise.
224  */
225 base.isEmptyObject = function(object) {
226   return Object.keys(object).length === 0;
230  * @type {boolean|undefined}
231  * @private
232  */
233 base.isAppsV2_ = undefined;
236  * @return {boolean} True if this is a v2 app; false if it is a legacy app.
237  */
238 base.isAppsV2 = function() {
239   if (base.isAppsV2_ === undefined) {
240     var manifest = chrome.runtime.getManifest();
241     base.isAppsV2_ =
242         Boolean(manifest && manifest.app && manifest.app.background);
243   }
244   return base.isAppsV2_;
248  * Joins the |url| with optional query parameters defined in |opt_params|
249  * See unit test for usage.
250  * @param {string} url
251  * @param {Object<string>=} opt_params
252  * @return {string}
253  */
254 base.urlJoin = function(url, opt_params) {
255   if (!opt_params) {
256     return url;
257   }
258   var queryParameters = [];
259   for (var key in opt_params) {
260     queryParameters.push(encodeURIComponent(key) + "=" +
261                          encodeURIComponent(opt_params[key]));
262   }
263   return url + '?' + queryParameters.join('&');
268  * @return {Object<string>} The URL parameters.
269  */
270 base.getUrlParameters = function() {
271   var result = {};
272   var parts = window.location.search.substring(1).split('&');
273   for (var i = 0; i < parts.length; i++) {
274     var pair = parts[i].split('=');
275     result[pair[0]] = decodeURIComponent(pair[1]);
276   }
277   return result;
281  * Convert special characters (e.g. &, < and >) to HTML entities.
283  * @param {string} str
284  * @return {string}
285  */
286 base.escapeHTML = function(str) {
287   var div = document.createElement('div');
288   div.appendChild(document.createTextNode(str));
289   return div.innerHTML;
293  * Promise is a great tool for writing asynchronous code. However, the construct
294  *   var p = new promise(function init(resolve, reject) {
295  *     ... // code that fulfills the Promise.
296  *   });
297  * forces the Promise-resolving logic to reside in the |init| function
298  * of the constructor.  This is problematic when you need to resolve the
299  * Promise in a member function(which is quite common for event callbacks).
301  * base.Deferred comes to the rescue.  It encapsulates a Promise
302  * object and exposes member methods (resolve/reject) to fulfill it.
304  * Here are the recommended steps to follow when implementing an asynchronous
305  * function that returns a Promise:
306  * 1. Create a deferred object by calling
307  *      var deferred = new base.Deferred();
308  * 2. Call deferred.resolve() when the asynchronous operation finishes.
309  * 3. Call deferred.reject() when the asynchronous operation fails.
310  * 4. Return deferred.promise() to the caller so that it can subscribe
311  *    to status changes using the |then| handler.
313  * Sample Usage:
314  *  function myAsyncAPI() {
315  *    var deferred = new base.Deferred();
316  *    window.setTimeout(function() {
317  *      deferred.resolve();
318  *    }, 100);
319  *    return deferred.promise();
320  *  };
322  * @constructor
323  * @template T
324  */
325 base.Deferred = function() {
326   /**
327    * @private {?function(?):void}
328    */
329   this.resolve_ = null;
331   /**
332    * @private {?function(?):void}
333    */
334   this.reject_ = null;
336   /**
337    * @this {base.Deferred}
338    * @param {function(?):void} resolve
339    * @param {function(*):void} reject
340    */
341   var initPromise = function(resolve, reject) {
342     this.resolve_ = resolve;
343     this.reject_ = reject;
344   };
346   /**
347    * @private {!Promise<T>}
348    */
349   this.promise_ = new Promise(initPromise.bind(this));
352 /** @param {*} reason */
353 base.Deferred.prototype.reject = function(reason) {
354   this.reject_(reason);
357 /** @param {*=} opt_value */
358 base.Deferred.prototype.resolve = function(opt_value) {
359   this.resolve_(opt_value);
362 /** @return {!Promise<T>} */
363 base.Deferred.prototype.promise = function() {
364   return this.promise_;
367 base.Promise = function() {};
370  * @param {number} delay
371  * @param {*=} opt_value
372  * @return {!Promise} a Promise that will be fulfilled with |opt_value|
373  *     after |delay| ms.
374  */
375 base.Promise.sleep = function(delay, opt_value) {
376   return new Promise(
377     function(resolve) {
378       window.setTimeout(function() {
379         resolve(opt_value);
380       }, delay);
381     });
385  * @param {Promise} promise
386  * @return {Promise} a Promise that will be fulfilled iff the specified Promise
387  *     is rejected.
388  */
389 base.Promise.negate = function(promise) {
390   return promise.then(
391       /** @return {Promise} */
392       function() {
393         return Promise.reject();
394       },
395       /** @return {Promise} */
396       function() {
397         return Promise.resolve();
398       });
402  * Creates a promise that will be fulfilled within a certain timeframe.
404  * This function creates a result promise |R| that will be resolved to
405  * either |promise| or |opt_defaultValue|.  If |promise| is fulfulled
406  * (i.e. resolved or rejected) within |delay| milliseconds, then |R|
407  * is resolved with |promise|.  Otherwise, |R| is resolved with
408  * |opt_defaultValue|.
410  * Avoid passing a promise as |opt_defaultValue|, as this could result
411  * in |R| remaining unfulfilled after |delay| milliseconds.
413  * @param {!Promise<T>} promise The promise to wrap.
414  * @param {number} delay The number of milliseconds to wait.
415  * @param {*=} opt_defaultValue The default value used to resolve the
416  *     result.
417  * @return {!Promise<T>} A new promise.
418  * @template T
419  */
420 base.Promise.withTimeout = function(promise, delay, opt_defaultValue) {
421   return Promise.race([promise, base.Promise.sleep(delay, opt_defaultValue)]);
425  * Converts a |method| with callbacks into a Promise.
427  * @param {Function} method
428  * @param {Array} params
429  * @param {*=} opt_context
430  * @param {boolean=} opt_hasErrorHandler whether the method has an error handler
431  * @return {Promise}
432  */
433 base.Promise.as = function(method, params, opt_context, opt_hasErrorHandler) {
434   return new Promise(function(resolve, reject) {
435     params.push(resolve);
436     if (opt_hasErrorHandler) {
437       params.push(reject);
438     }
439     try {
440       method.apply(opt_context, params);
441     } catch (/** @type {*} */ e) {
442       reject(e);
443     }
444   });
448  * A mixin for classes with events.
450  * For example, to create an alarm event for SmokeDetector:
451  * functionSmokeDetector() {
452  *    base.inherits(this, base.EventSourceImpl);
453  *    this.defineEvents(['alarm']);
454  * };
456  * To fire an event:
457  * SmokeDetector.prototype.onCarbonMonoxideDetected = function() {
458  *   var param = {} // optional parameters
459  *   this.raiseEvent('alarm', param);
460  * }
462  * To listen to an event:
463  * var smokeDetector = new SmokeDetector();
464  * smokeDetector.addEventListener('alarm', listenerObj.someCallback)
466  */
469   * Helper interface for the EventSource.
470   * @constructor
471   */
472 base.EventEntry = function() {
473   /** @type {Array<function():void>} */
474   this.listeners = [];
478 /** @interface */
479 base.EventSource = function() {};
481  /**
482   * Add a listener |fn| to listen to |type| event.
483   * @param {string} type
484   * @param {Function} fn
485   */
486 base.EventSource.prototype.addEventListener = function(type, fn) {};
488  /**
489   * Remove a listener |fn| to listen to |type| event.
490   * @param {string} type
491   * @param {Function} fn
492   */
493 base.EventSource.prototype.removeEventListener = function(type, fn) {};
497   * @constructor
498   * Since this class is implemented as a mixin, the constructor may not be
499   * called.  All initializations should be done in defineEvents.
500   * @implements {base.EventSource}
501   */
502 base.EventSourceImpl = function() {
503   /** @type {Object<base.EventEntry>} */
504   this.eventMap_;
508   * @param {base.EventSourceImpl} obj
509   * @param {string} type
510   * @private
511   */
512 base.EventSourceImpl.assertHasEvent_ = function(obj, type) {
513   console.assert(Boolean(obj.eventMap_),
514                  "The object doesn't support events.");
515   console.assert(Boolean(obj.eventMap_[type]),
516                  'Event <' + type +'> is undefined for the current object.');
519 base.EventSourceImpl.prototype = {
520   /**
521     * Define |events| for this event source.
522     * @param {Array<string>} events
523     */
524   defineEvents: function(events) {
525     console.assert(!Boolean(this.eventMap_),
526                    'defineEvents() can only be called once.');
527     this.eventMap_ = {};
528     events.forEach(
529       /**
530         * @this {base.EventSourceImpl}
531         * @param {string} type
532         */
533       function(type) {
534         console.assert(typeof type == 'string',
535                        'Event name must be a string; found ' + type + '.');
536         this.eventMap_[type] = new base.EventEntry();
537     }, this);
538   },
540   /**
541     * @param {string} type
542     * @param {Function} fn
543     */
544   addEventListener: function(type, fn) {
545     console.assert(typeof fn == 'function',
546                    'addEventListener(): event listener for ' + type +
547                    ' must be function, not ' + typeof fn + '.');
548     base.EventSourceImpl.assertHasEvent_(this, type);
550     var listeners = this.eventMap_[type].listeners;
551     listeners.push(fn);
552   },
554   /**
555     * @param {string} type
556     * @param {Function} fn
557     */
558   removeEventListener: function(type, fn) {
559     console.assert(typeof fn == 'function',
560                    'removeEventListener(): event listener for ' + type +
561                    ' must be function, not ' + typeof fn + '.');
562     base.EventSourceImpl.assertHasEvent_(this, type);
564     var listeners = this.eventMap_[type].listeners;
565     // find the listener to remove.
566     for (var i = 0; i < listeners.length; i++) {
567       var listener = listeners[i];
568       if (listener == fn) {
569         listeners.splice(i, 1);
570         break;
571       }
572     }
573   },
575   /**
576     * Fire an event of a particular type on this object.
577     * @param {string} type
578     * @param {*=} opt_details The type of |opt_details| should be ?= to
579     *     match what is defined in add(remove)EventListener.  However, JSCompile
580     *     cannot handle invoking an unknown type as an argument to |listener|
581     *     As a hack, we set the type to *=.
582     */
583   raiseEvent: function(type, opt_details) {
584     base.EventSourceImpl.assertHasEvent_(this, type);
586     var entry = this.eventMap_[type];
587     var listeners = entry.listeners.slice(0); // Make a copy of the listeners.
589     listeners.forEach(
590       /** @param {function(*=):void} listener */
591       function(listener){
592         if (listener) {
593           listener(opt_details);
594         }
595     });
596   }
601   * A lightweight object that helps manage the lifetime of an event listener.
602   *
603   * For example, do the following if you want to automatically unhook events
604   * when your object is disposed:
605   *
606   * var MyConstructor = function(domElement) {
607   *   this.eventHooks_ = new base.Disposables(
608   *     new base.EventHook(domElement, 'click', this.onClick_.bind(this)),
609   *     new base.EventHook(domElement, 'keydown', this.onClick_.bind(this)),
610   *     new base.ChromeEventHook(chrome.runtime.onMessage,
611   *                              this.onMessage_.bind(this))
612   *   );
613   * }
614   *
615   * MyConstructor.prototype.dispose = function() {
616   *   this.eventHooks_.dispose();
617   *   this.eventHooks_ = null;
618   * }
619   *
620   * @param {base.EventSource} src
621   * @param {string} eventName
622   * @param {Function} listener
623   *
624   * @constructor
625   * @implements {base.Disposable}
626   */
627 base.EventHook = function(src, eventName, listener) {
628   this.src_ = src;
629   this.eventName_ = eventName;
630   this.listener_ = listener;
631   src.addEventListener(eventName, listener);
634 base.EventHook.prototype.dispose = function() {
635   this.src_.removeEventListener(this.eventName_, this.listener_);
639   * An event hook implementation for DOM Events.
640   *
641   * @param {HTMLElement|Element|Window|HTMLDocument} src
642   * @param {string} eventName
643   * @param {Function} listener
644   * @param {boolean} capture
645   *
646   * @constructor
647   * @implements {base.Disposable}
648   */
649 base.DomEventHook = function(src, eventName, listener, capture) {
650   this.src_ = src;
651   this.eventName_ = eventName;
652   this.listener_ = listener;
653   this.capture_ = capture;
654   src.addEventListener(eventName, listener, capture);
657 base.DomEventHook.prototype.dispose = function() {
658   this.src_.removeEventListener(this.eventName_, this.listener_, this.capture_);
663   * An event hook implementation for Chrome Events.
664   *
665   * @param {ChromeEvent|
666   *         chrome.contextMenus.ClickedEvent|
667   *         chrome.app.runtime.LaunchEvent} src
668   * @param {!Function} listener
669   *
670   * @constructor
671   * @implements {base.Disposable}
672   */
673 base.ChromeEventHook = function(src, listener) {
674   this.src_ = src;
675   this.listener_ = listener;
676   src.addListener(listener);
679 base.ChromeEventHook.prototype.dispose = function() {
680   this.src_.removeListener(this.listener_);
684  * A disposable repeating timer.
686  * @param {Function} callback
687  * @param {number} interval
688  * @param {boolean=} opt_invokeNow Whether to invoke the callback now, default
689  *    to false.
691  * @constructor
692  * @implements {base.Disposable}
693  */
694 base.RepeatingTimer = function(callback, interval, opt_invokeNow) {
695   /** @private */
696   this.intervalId_ = window.setInterval(callback, interval);
697   if (opt_invokeNow) {
698     callback();
699   }
702 base.RepeatingTimer.prototype.dispose = function() {
703   window.clearInterval(this.intervalId_);
704   this.intervalId_ = null;
708  * A disposable one shot timer.
710  * @param {Function} callback
711  * @param {number} timeout
713  * @constructor
714  * @implements {base.Disposable}
715  */
716 base.OneShotTimer = function(callback, timeout) {
717   var that = this;
719   /** @private */
720   this.timerId_ = window.setTimeout(function() {
721     that.timerId_ = null;
722     callback();
723   }, timeout);
726 base.OneShotTimer.prototype.dispose = function() {
727   if (this.timerId_ !== null) {
728     window.clearTimeout(this.timerId_);
729     this.timerId_ = null;
730   }
734   * Converts UTF-8 string to ArrayBuffer.
735   *
736   * @param {string} string
737   * @return {ArrayBuffer}
738   */
739 base.encodeUtf8 = function(string) {
740   var utf8String = unescape(encodeURIComponent(string));
741   var result = new Uint8Array(utf8String.length);
742   for (var i = 0; i < utf8String.length; i++)
743     result[i] = utf8String.charCodeAt(i);
744   return result.buffer;
748   * Decodes UTF-8 string from ArrayBuffer.
749   *
750   * @param {ArrayBuffer} buffer
751   * @return {string}
752   */
753 base.decodeUtf8 = function(buffer) {
754   return decodeURIComponent(
755       escape(String.fromCharCode.apply(null, new Uint8Array(buffer))));
759  * Generate a nonce, to be used as an xsrf protection token.
761  * @return {string} A URL-Safe Base64-encoded 128-bit random value. */
762 base.generateXsrfToken = function() {
763   var random = new Uint8Array(16);
764   window.crypto.getRandomValues(random);
765   var base64Token = window.btoa(String.fromCharCode.apply(null, random));
766   return base64Token.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
770  * @return {string} A random UUID.
771  */
772 base.generateUuid = function() {
773   var random = new Uint16Array(8);
774   window.crypto.getRandomValues(random);
775   /** @type {Array<string>} */
776   var e = new Array();
777   for (var i = 0; i < 8; i++) {
778     e[i] = (/** @type {number} */ (random[i]) + 0x10000).
779         toString(16).substring(1);
780   }
781   return e[0] + e[1] + '-' + e[2] + '-' + e[3] + '-' +
782       e[4] + '-' + e[5] + e[6] + e[7];
786  * @param {string} jsonString A JSON-encoded string.
787  * @return {Object|undefined} The decoded object, or undefined if the string
788  *     cannot be parsed.
789  */
790 base.jsonParseSafe = function(jsonString) {
791   try {
792     return /** @type {Object} */ (JSON.parse(jsonString));
793   } catch (err) {
794     return undefined;
795   }
799  * Return the current time as a formatted string suitable for logging.
801  * @return {string} The current time, formatted as the standard ISO string.
802  *     [yyyy-mm-ddDhh:mm:ss.xyz]
803  */
804 base.timestamp = function() {
805   return '[' + new Date().toISOString() + ']';
810  * A online function that can be stubbed by unit tests.
811  * @return {boolean}
812  */
813 base.isOnline = function() {
814   return navigator.onLine;
818  * Size the current window to fit its content.
819  * @param {boolean=} opt_centerWindow If true, position the window in the
820  *     center of the screen after resizing it.
821  */
822 base.resizeWindowToContent = function(opt_centerWindow) {
823   var appWindow = chrome.app.window.current();
824   var borderX = appWindow.outerBounds.width - appWindow.innerBounds.width;
825   var borderY = appWindow.outerBounds.height - appWindow.innerBounds.height;
826   var width = Math.ceil(document.documentElement.scrollWidth + borderX);
827   var height = Math.ceil(document.documentElement.scrollHeight + borderY);
828   appWindow.outerBounds.width = width;
829   appWindow.outerBounds.height = height;
830   if (opt_centerWindow) {
831     var screenWidth = screen.availWidth;
832     var screenHeight = screen.availHeight;
833     appWindow.outerBounds.left = Math.round((screenWidth - width) / 2);
834     appWindow.outerBounds.top = Math.round((screenHeight - height) / 2);
835   }