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.
7 * A module that contains basic utility components and methods for the
14 /** @suppress {duplicate} */
15 var base
= base
|| {};
17 base
.debug = function() {};
20 * Whether to break in debugger and alert when an assertion fails.
21 * Set it to true for debugging.
24 base
.debug
.throwOnAssert
= false;
27 * Assert that |expr| is true else print the |opt_msg|.
28 * @param {boolean} expr
29 * @param {string=} opt_msg
31 base
.debug
.assert = function(expr
, opt_msg
) {
34 opt_msg
= 'Assertion Failed.';
36 console
.error(opt_msg
);
37 if (base
.debug
.throwOnAssert
) {
38 throw new Error(opt_msg
);
44 * @return {string} The callstack of the current method.
46 base
.debug
.callstack = function() {
49 } catch (/** @type {Error} */ error
) {
50 var callstack
= error
.stack
51 .replace(/^\s+(at eval )?at\s+/gm, '') // Remove 'at' and indentation.
53 callstack
.splice(0,2); // Remove the stack of the current function.
55 return callstack
.join('\n');
61 base
.Disposable = function() {};
62 base
.Disposable
.prototype.dispose = function() {};
66 * @param {...base.Disposable} var_args
67 * @implements {base.Disposable}
69 base
.Disposables = function(var_args
) {
71 * @type {Array<base.Disposable>}
74 this.disposables_
= Array
.prototype.slice
.call(arguments
, 0);
78 * @param {...base.Disposable} var_args
80 base
.Disposables
.prototype.add = function(var_args
) {
81 var disposables
= Array
.prototype.slice
.call(arguments
, 0);
82 for (var i
= 0; i
< disposables
.length
; i
++) {
83 var current
= /** @type {base.Disposable} */ (disposables
[i
]);
84 if (this.disposables_
.indexOf(current
) === -1) {
85 this.disposables_
.push(current
);
91 * @param {...base.Disposable} var_args Dispose |var_args| and remove
92 * them from the current object.
94 base
.Disposables
.prototype.remove = function(var_args
) {
95 var disposables
= Array
.prototype.slice
.call(arguments
, 0);
96 for (var i
= 0; i
< disposables
.length
; i
++) {
97 var disposable
= /** @type {base.Disposable} */ (disposables
[i
]);
98 var index
= this.disposables_
.indexOf(disposable
);
100 this.disposables_
.splice(index
, 1);
101 disposable
.dispose();
106 base
.Disposables
.prototype.dispose = function() {
107 for (var i
= 0; i
< this.disposables_
.length
; i
++) {
108 this.disposables_
[i
].dispose();
110 this.disposables_
= null;
114 * A utility function to invoke |obj|.dispose without a null check on |obj|.
115 * @param {base.Disposable} obj
117 base
.dispose = function(obj
) {
119 base
.debug
.assert(typeof obj
.dispose
== 'function');
125 * Copy all properties from src to dest.
126 * @param {Object} dest
127 * @param {Object} src
129 base
.mix = function(dest
, src
) {
130 for (var prop
in src
) {
131 if (src
.hasOwnProperty(prop
) && !(prop
in dest
)) {
132 dest
[prop
] = src
[prop
];
138 * Adds a mixin to a class.
139 * @param {Object} dest
140 * @param {Object} src
141 * @suppress {checkTypes|reportUnknownTypes}
143 base
.extend = function(dest
, src
) {
144 base
.mix(dest
.prototype, src
.prototype || src
);
148 * Inherits properties and methods from |parentCtor| at object construction time
149 * using prototypical inheritance. e.g.
151 * var ParentClass = function(parentArg) {
152 * this.parentProperty = parentArg;
155 * var ChildClass = function() {
156 * base.inherits(this, ParentClass, 'parentArg'); // must be the first line.
159 * var child = new ChildClass();
160 * child instanceof ParentClass // true
162 * See base_inherits_unittest.js for the guaranteed behavior of base.inherits().
163 * This lazy approach is chosen so that it is not necessary to maintain proper
164 * script loading order between the parent class and the child class.
166 * @param {*} childObject
167 * @param {*} parentCtor
168 * @param {...} parentCtorArgs
169 * @suppress {checkTypes|reportUnknownTypes}
171 base
.inherits = function(childObject
, parentCtor
, parentCtorArgs
) {
172 base
.debug
.assert(parentCtor
&& parentCtor
.prototype,
173 'Invalid parent constructor.');
174 var parentArgs
= Array
.prototype.slice
.call(arguments
, 2);
176 // Mix in the parent's prototypes so that they're available during the parent
178 base
.mix(childObject
, parentCtor
.prototype);
179 parentCtor
.apply(childObject
, parentArgs
);
181 // Note that __proto__ is deprecated.
182 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
183 // Global_Objects/Object/proto.
184 // It is used so that childObject instanceof parentCtor will
186 childObject
.__proto__
.__proto__
= parentCtor
.prototype;
187 base
.debug
.assert(childObject
instanceof parentCtor
);
190 base
.doNothing = function() {};
193 * Returns an array containing the values of |dict|.
194 * @param {!Object} dict
197 base
.values = function(dict
) {
198 return Object
.keys(dict
).map(
199 /** @param {string} key */
207 * @return {*} a recursive copy of |value| or null if |value| is not copyable
208 * (e.g. undefined, NaN).
210 base
.deepCopy = function(value
) {
212 return JSON
.parse(JSON
.stringify(value
));
218 * Returns a copy of the input object with all null/undefined fields
219 * removed. Returns an empty object for a null/undefined input.
221 * @param {Object<string,?T>|undefined} input
222 * @return {!Object<string,T>}
225 base
.copyWithoutNullFields = function(input
) {
226 /** @const {!Object} */
229 for (var field
in input
) {
230 var value
= /** @type {*} */ (input
[field
]);
232 result
[field
] = value
;
240 * @type {boolean|undefined}
243 base
.isAppsV2_
= undefined;
246 * @return {boolean} True if this is a v2 app; false if it is a legacy app.
248 base
.isAppsV2 = function() {
249 if (base
.isAppsV2_
=== undefined) {
250 var manifest
= chrome
.runtime
.getManifest();
252 Boolean(manifest
&& manifest
.app
&& manifest
.app
.background
);
254 return base
.isAppsV2_
;
258 * Joins the |url| with optional query parameters defined in |opt_params|
259 * See unit test for usage.
260 * @param {string} url
261 * @param {Object<string>=} opt_params
264 base
.urlJoin = function(url
, opt_params
) {
268 var queryParameters
= [];
269 for (var key
in opt_params
) {
270 queryParameters
.push(encodeURIComponent(key
) + "=" +
271 encodeURIComponent(opt_params
[key
]));
273 return url
+ '?' + queryParameters
.join('&');
278 * @return {Object<string, string>} The URL parameters.
280 base
.getUrlParameters = function() {
282 var parts
= window
.location
.search
.substring(1).split('&');
283 for (var i
= 0; i
< parts
.length
; i
++) {
284 var pair
= parts
[i
].split('=');
285 result
[pair
[0]] = decodeURIComponent(pair
[1]);
291 * Convert special characters (e.g. &, < and >) to HTML entities.
293 * @param {string} str
296 base
.escapeHTML = function(str
) {
297 var div
= document
.createElement('div');
298 div
.appendChild(document
.createTextNode(str
));
299 return div
.innerHTML
;
303 * Promise is a great tool for writing asynchronous code. However, the construct
304 * var p = new promise(function init(resolve, reject) {
305 * ... // code that fulfills the Promise.
307 * forces the Promise-resolving logic to reside in the |init| function
308 * of the constructor. This is problematic when you need to resolve the
309 * Promise in a member function(which is quite common for event callbacks).
311 * base.Deferred comes to the rescue. It encapsulates a Promise
312 * object and exposes member methods (resolve/reject) to fulfill it.
314 * Here are the recommended steps to follow when implementing an asynchronous
315 * function that returns a Promise:
316 * 1. Create a deferred object by calling
317 * var deferred = new base.Deferred();
318 * 2. Call deferred.resolve() when the asynchronous operation finishes.
319 * 3. Call deferred.reject() when the asynchronous operation fails.
320 * 4. Return deferred.promise() to the caller so that it can subscribe
321 * to status changes using the |then| handler.
324 * function myAsyncAPI() {
325 * var deferred = new base.Deferred();
326 * window.setTimeout(function() {
327 * deferred.resolve();
329 * return deferred.promise();
335 base
.Deferred = function() {
337 * @private {?function(?):void}
339 this.resolve_
= null;
342 * @private {?function(?):void}
347 * @this {base.Deferred}
348 * @param {function(?):void} resolve
349 * @param {function(*):void} reject
351 var initPromise = function(resolve
, reject
) {
352 this.resolve_
= resolve
;
353 this.reject_
= reject
;
357 * @private {!Promise<T>}
359 this.promise_
= new Promise(initPromise
.bind(this));
362 /** @param {*} reason */
363 base
.Deferred
.prototype.reject = function(reason
) {
364 this.reject_(reason
);
367 /** @param {*=} opt_value */
368 base
.Deferred
.prototype.resolve = function(opt_value
) {
369 this.resolve_(opt_value
);
372 /** @return {!Promise<T>} */
373 base
.Deferred
.prototype.promise = function() {
374 return this.promise_
;
377 base
.Promise = function() {};
380 * @param {number} delay
381 * @return {Promise} a Promise that will be fulfilled after |delay| ms.
383 base
.Promise
.sleep = function(delay
) {
385 /** @param {function(*):void} fulfill */
387 window
.setTimeout(fulfill
, delay
);
393 * @param {Promise} promise
394 * @return {Promise} a Promise that will be fulfilled iff the specified Promise
397 base
.Promise
.negate = function(promise
) {
399 /** @return {Promise} */
401 return Promise
.reject();
403 /** @return {Promise} */
405 return Promise
.resolve();
410 * Converts a |method| with callbacks into a Promise.
412 * @param {Function} method
413 * @param {Array} params
414 * @param {*=} opt_context
415 * @param {boolean=} opt_hasErrorHandler whether the method has an error handler
418 base
.Promise
.as = function(method
, params
, opt_context
, opt_hasErrorHandler
) {
419 return new Promise(function(resolve
, reject
) {
420 params
.push(resolve
);
421 if (opt_hasErrorHandler
) {
425 method
.apply(opt_context
, params
);
426 } catch (/** @type {*} */ e
) {
433 * A mixin for classes with events.
435 * For example, to create an alarm event for SmokeDetector:
436 * functionSmokeDetector() {
437 * base.inherits(this, base.EventSourceImpl);
438 * this.defineEvents(['alarm']);
442 * SmokeDetector.prototype.onCarbonMonoxideDetected = function() {
443 * var param = {} // optional parameters
444 * this.raiseEvent('alarm', param);
447 * To listen to an event:
448 * var smokeDetector = new SmokeDetector();
449 * smokeDetector.addEventListener('alarm', listenerObj.someCallback)
454 * Helper interface for the EventSource.
457 base
.EventEntry = function() {
458 /** @type {Array<function():void>} */
464 base
.EventSource = function() {};
467 * Add a listener |fn| to listen to |type| event.
468 * @param {string} type
469 * @param {Function} fn
471 base
.EventSource
.prototype.addEventListener = function(type
, fn
) {};
474 * Remove a listener |fn| to listen to |type| event.
475 * @param {string} type
476 * @param {Function} fn
478 base
.EventSource
.prototype.removeEventListener = function(type
, fn
) {};
483 * Since this class is implemented as a mixin, the constructor may not be
484 * called. All initializations should be done in defineEvents.
485 * @implements {base.EventSource}
487 base
.EventSourceImpl = function() {
488 /** @type {Object<string, base.EventEntry>} */
493 * @param {base.EventSourceImpl} obj
494 * @param {string} type
496 base
.EventSourceImpl
.isDefined = function(obj
, type
) {
497 base
.debug
.assert(Boolean(obj
.eventMap_
),
498 "The object doesn't support events");
499 base
.debug
.assert(Boolean(obj
.eventMap_
[type
]), 'Event <' + type
+
500 '> is undefined for the current object');
503 base
.EventSourceImpl
.prototype = {
505 * Define |events| for this event source.
506 * @param {Array<string>} events
508 defineEvents: function(events
) {
509 base
.debug
.assert(!Boolean(this.eventMap_
),
510 'defineEvents can only be called once.');
514 * @this {base.EventSourceImpl}
515 * @param {string} type
518 base
.debug
.assert(typeof type
== 'string');
519 this.eventMap_
[type
] = new base
.EventEntry();
524 * @param {string} type
525 * @param {Function} fn
527 addEventListener: function(type
, fn
) {
528 base
.debug
.assert(typeof fn
== 'function');
529 base
.EventSourceImpl
.isDefined(this, type
);
531 var listeners
= this.eventMap_
[type
].listeners
;
536 * @param {string} type
537 * @param {Function} fn
539 removeEventListener: function(type
, fn
) {
540 base
.debug
.assert(typeof fn
== 'function');
541 base
.EventSourceImpl
.isDefined(this, type
);
543 var listeners
= this.eventMap_
[type
].listeners
;
544 // find the listener to remove.
545 for (var i
= 0; i
< listeners
.length
; i
++) {
546 var listener
= listeners
[i
];
547 if (listener
== fn
) {
548 listeners
.splice(i
, 1);
555 * Fire an event of a particular type on this object.
556 * @param {string} type
557 * @param {*=} opt_details The type of |opt_details| should be ?= to
558 * match what is defined in add(remove)EventListener. However, JSCompile
559 * cannot handle invoking an unknown type as an argument to |listener|
560 * As a hack, we set the type to *=.
562 raiseEvent: function(type
, opt_details
) {
563 base
.EventSourceImpl
.isDefined(this, type
);
565 var entry
= this.eventMap_
[type
];
566 var listeners
= entry
.listeners
.slice(0); // Make a copy of the listeners.
569 /** @param {function(*=):void} listener */
572 listener(opt_details
);
580 * A lightweight object that helps manage the lifetime of an event listener.
582 * For example, do the following if you want to automatically unhook events
583 * when your object is disposed:
585 * var MyConstructor = function(domElement) {
586 * this.eventHooks_ = new base.Disposables(
587 * new base.EventHook(domElement, 'click', this.onClick_.bind(this)),
588 * new base.EventHook(domElement, 'keydown', this.onClick_.bind(this)),
589 * new base.ChromeEventHook(chrome.runtime.onMessage,
590 * this.onMessage_.bind(this))
594 * MyConstructor.prototype.dispose = function() {
595 * this.eventHooks_.dispose();
596 * this.eventHooks_ = null;
599 * @param {base.EventSource} src
600 * @param {string} eventName
601 * @param {Function} listener
604 * @implements {base.Disposable}
606 base
.EventHook = function(src
, eventName
, listener
) {
608 this.eventName_
= eventName
;
609 this.listener_
= listener
;
610 src
.addEventListener(eventName
, listener
);
613 base
.EventHook
.prototype.dispose = function() {
614 this.src_
.removeEventListener(this.eventName_
, this.listener_
);
618 * An event hook implementation for DOM Events.
620 * @param {HTMLElement|Element|Window|HTMLDocument} src
621 * @param {string} eventName
622 * @param {Function} listener
623 * @param {boolean} capture
626 * @implements {base.Disposable}
628 base
.DomEventHook = function(src
, eventName
, listener
, capture
) {
630 this.eventName_
= eventName
;
631 this.listener_
= listener
;
632 this.capture_
= capture
;
633 src
.addEventListener(eventName
, listener
, capture
);
636 base
.DomEventHook
.prototype.dispose = function() {
637 this.src_
.removeEventListener(this.eventName_
, this.listener_
, this.capture_
);
642 * An event hook implementation for Chrome Events.
644 * @param {chrome.Event} src
645 * @param {Function} listener
648 * @implements {base.Disposable}
650 base
.ChromeEventHook = function(src
, listener
) {
652 this.listener_
= listener
;
653 src
.addListener(listener
);
656 base
.ChromeEventHook
.prototype.dispose = function() {
657 this.src_
.removeListener(this.listener_
);
661 * A disposable repeating timer.
663 * @param {Function} callback
664 * @param {number} interval
665 * @param {boolean=} opt_invokeNow Whether to invoke the callback now, default
669 * @implements {base.Disposable}
671 base
.RepeatingTimer = function(callback
, interval
, opt_invokeNow
) {
673 this.intervalId_
= window
.setInterval(callback
, interval
);
679 base
.RepeatingTimer
.prototype.dispose = function() {
680 window
.clearInterval(this.intervalId_
);
681 this.intervalId_
= null;
685 * A disposable one shot timer.
687 * @param {Function} callback
688 * @param {number} timeout
691 * @implements {base.Disposable}
693 base
.OneShotTimer = function(callback
, timeout
) {
697 this.timerId_
= window
.setTimeout(function() {
698 that
.timerId_
= null;
703 base
.OneShotTimer
.prototype.dispose = function() {
704 if (this.timerId_
!== null) {
705 window
.clearTimeout(this.timerId_
);
706 this.timerId_
= null;
711 * Converts UTF-8 string to ArrayBuffer.
713 * @param {string} string
714 * @return {ArrayBuffer}
716 base
.encodeUtf8 = function(string
) {
717 var utf8String
= unescape(encodeURIComponent(string
));
718 var result
= new Uint8Array(utf8String
.length
);
719 for (var i
= 0; i
< utf8String
.length
; i
++)
720 result
[i
] = utf8String
.charCodeAt(i
);
721 return result
.buffer
;
725 * Decodes UTF-8 string from ArrayBuffer.
727 * @param {ArrayBuffer} buffer
730 base
.decodeUtf8 = function(buffer
) {
731 return decodeURIComponent(
732 escape(String
.fromCharCode
.apply(null, new Uint8Array(buffer
))));
736 * Generate a nonce, to be used as an xsrf protection token.
738 * @return {string} A URL-Safe Base64-encoded 128-bit random value. */
739 base
.generateXsrfToken = function() {
740 var random
= new Uint8Array(16);
741 window
.crypto
.getRandomValues(random
);
742 var base64Token
= window
.btoa(String
.fromCharCode
.apply(null, random
));
743 return base64Token
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g
, '');
747 * @return {string} A random UUID.
749 base
.generateUuid = function() {
750 var random
= new Uint16Array(8);
751 window
.crypto
.getRandomValues(random
);
752 /** @type {Array<string>} */
754 for (var i
= 0; i
< 8; i
++) {
755 e
[i
] = (/** @type {number} */ (random
[i
]) + 0x10000).
756 toString(16).substring(1);
758 return e
[0] + e
[1] + '-' + e
[2] + '-' + e
[3] + '-' +
759 e
[4] + '-' + e
[5] + e
[6] + e
[7];
763 * @param {string} jsonString A JSON-encoded string.
764 * @return {Object|undefined} The decoded object, or undefined if the string
767 base
.jsonParseSafe = function(jsonString
) {
769 return /** @type {Object} */ (JSON
.parse(jsonString
));
776 * Return the current time as a formatted string suitable for logging.
778 * @return {string} The current time, formatted as the standard ISO string.
779 * [yyyy-mm-ddDhh:mm:ss.xyz]
781 base
.timestamp = function() {
782 return '[' + new Date().toISOString() + ']';
786 * Size the current window to fit its content.
787 * @param {boolean=} opt_centerWindow If true, position the window in the
788 * center of the screen after resizing it.
790 base
.resizeWindowToContent = function(opt_centerWindow
) {
791 var appWindow
= chrome
.app
.window
.current();
792 var outerBounds
= appWindow
.outerBounds
;
793 var borderX
= outerBounds
.width
- appWindow
.innerBounds
.width
;
794 var borderY
= outerBounds
.height
- appWindow
.innerBounds
.height
;
795 var newWidth
= document
.documentElement
.scrollWidth
+ borderX
;
796 var newHeight
= document
.documentElement
.scrollHeight
+ borderY
;
797 appWindow
.resizeTo(newWidth
, newHeight
);
798 var left
= outerBounds
.left
;
799 var top
= outerBounds
.top
;
800 if (opt_centerWindow
) {
801 var screenWidth
= screen
.availWidth
;
802 var screenHeight
= screen
.availHeight
;
803 left
= (screenWidth
- newWidth
) / 2;
804 top
= (screenHeight
- newHeight
) / 2;
806 // Sometimes, resizing the window causes its position to be reset to (0, 0),
807 // so restore it explicitly, even if it doesn't need to be centered.
808 appWindow
.moveTo(left
, top
);