12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
5868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)'use strict';
6c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/**
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @fileoverview Utility objects and functions for Google Now extension.
9424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * Most important entities here:
10424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * (1) 'wrapper' is a module used to add error handling and other services to
11424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     callbacks for HTML and Chrome functions and Chrome event listeners.
12424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     Chrome invokes extension code through event listeners. Once entered via
13424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     an event listener, the extension may call a Chrome/HTML API method
14424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     passing a callback (and so forth), and that callback must occur later,
15424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     otherwise, we generate an error. Chrome may unload event pages waiting
16424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     for an event. When the event fires, Chrome will reload the event page. We
17424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     don't require event listeners to fire because they are generally not
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) *     predictable (like a button clicked event).
19424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * (2) Task Manager (built with buildTaskManager() call) provides controlling
20424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     mutually excluding chains of callbacks called tasks. Task Manager uses
21424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     WrapperPlugins to add instrumentation code to 'wrapper' to determine
22424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     when a task completes.
232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
258bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// TODO(vadimt): Use server name in the manifest.
263240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch
2790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
2890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Notification server URL.
2990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
300f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)var NOTIFICATION_CARDS_URL = 'https://www.googleapis.com/chromenow/v1';
3190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)/**
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Returns true if debug mode is enabled.
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * localStorage returns items as strings, which means if we store a boolean,
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * it returns a string. Use this function to compare against true.
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * @return {boolean} Whether debug mode is enabled.
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)function isInDebugMode() {
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return localStorage.debug_mode === 'true';
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
413240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch
423240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch/**
438bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) * Initializes for debug or release modes of operation.
448bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) */
458bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)function initializeDebug() {
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (isInDebugMode()) {
478bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)    NOTIFICATION_CARDS_URL =
488bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)        localStorage['server_url'] || NOTIFICATION_CARDS_URL;
498bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  }
508bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)}
518bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
528bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)initializeDebug();
538bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
540f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)/**
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Conditionally allow console.log output based off of the debug mode.
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)console.log = function() {
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  var originalConsoleLog = console.log;
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return function() {
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (isInDebugMode()) {
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      originalConsoleLog.apply(console, arguments);
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  };
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}();
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)/**
675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Explanation Card Storage.
680f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) */
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)if (localStorage['explanatoryCardsShown'] === undefined)
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  localStorage['explanatoryCardsShown'] = 0;
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)/**
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Location Card Count Cleanup.
745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)if (localStorage.locationCardsShown !== undefined)
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  localStorage.removeItem('locationCardsShown');
770f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
788bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)/**
793551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * Builds an error object with a message that may be sent to the server.
803551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * @param {string} message Error message. This message may be sent to the
813551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) *     server.
823551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * @return {Error} Error object.
833240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch */
843551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)function buildErrorWithMessageForServer(message) {
853551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  var error = new Error(message);
863551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  error.canSendMessageToServer = true;
873551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  return error;
883240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch}
893240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/**
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * Checks for internal errors.
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {boolean} condition Condition that must be true.
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {string} message Diagnostic message for the case when the condition is
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) *     false.
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)function verify(condition, message) {
97c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if (!condition)
983551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    throw buildErrorWithMessageForServer('ASSERT: ' + message);
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/**
10290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Builds a request to the notification server.
1034e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) * @param {string} method Request method.
10490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {string} handlerName Server handler to send the request to.
105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param {string=} opt_contentType Value for the Content-type header.
10690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @return {XMLHttpRequest} Server request.
10790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)function buildServerRequest(method, handlerName, opt_contentType) {
10990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  var request = new XMLHttpRequest();
11090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
11190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  request.responseType = 'text';
1124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  request.open(method, NOTIFICATION_CARDS_URL + '/' + handlerName, true);
113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (opt_contentType)
114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    request.setRequestHeader('Content-type', opt_contentType);
11590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
11690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  return request;
11790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
11890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
11990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
120424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * Sends an error report to the server.
121424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * @param {Error} error Error to send.
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
123424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)function sendErrorReport(error) {
124424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  // Don't remove 'error.stack.replace' below!
125424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var filteredStack = error.canSendMessageToServer ?
126424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      error.stack : error.stack.replace(/.*\n/, '(message removed)\n');
127424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var file;
128424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var line;
129424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var topFrameLineMatch = filteredStack.match(/\n    at .*\n/);
130424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var topFrame = topFrameLineMatch && topFrameLineMatch[0];
131424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  if (topFrame) {
132424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // Examples of a frame:
133424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // 1. '\n    at someFunction (chrome-extension://
1348bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)    //     pafkbggdmjlpgkdkcbjmhmfcdpncadgh/background.js:915:15)\n'
1358bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)    // 2. '\n    at chrome-extension://pafkbggdmjlpgkdkcbjmhmfcdpncadgh/
136424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    //     utility.js:269:18\n'
137424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // 3. '\n    at Function.target.(anonymous function) (extensions::
138424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    //     SafeBuiltins:19:14)\n'
139424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // 4. '\n    at Event.dispatchToListener (event_bindings:382:22)\n'
140424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    var errorLocation;
141424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // Find the the parentheses at the end of the line, if any.
142424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    var parenthesesMatch = topFrame.match(/\(.*\)\n/);
143424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (parenthesesMatch && parenthesesMatch[0]) {
144424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      errorLocation =
145424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)          parenthesesMatch[0].substring(1, parenthesesMatch[0].length - 2);
146424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    } else {
147424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      errorLocation = topFrame;
148424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    }
1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
150424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    var topFrameElements = errorLocation.split(':');
151424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // topFrameElements is an array that ends like:
1528bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)    // [N-3] //pafkbggdmjlpgkdkcbjmhmfcdpncadgh/utility.js
153424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // [N-2] 308
154424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // [N-1] 19
155424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (topFrameElements.length >= 3) {
156424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      file = topFrameElements[topFrameElements.length - 3];
157424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      line = topFrameElements[topFrameElements.length - 2];
158424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    }
159424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  }
160558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch
16158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  var errorText = error.name;
16258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  if (error.canSendMessageToServer)
16358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    errorText = errorText + ': ' + error.message;
16458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)
1654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  var errorObject = {
1664e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    message: errorText,
1674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    file: file,
1684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    line: line,
1694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    trace: filteredStack
1704e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  };
1714e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
172a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // We use relatively direct calls here because the instrumentation may be in
173a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // a bad state. Wrappers and promises should not be involved in the reporting.
1744e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  var request = buildServerRequest('POST', 'jserrors', 'application/json');
175424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  request.onloadend = function(event) {
176424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    console.log('sendErrorReport status: ' + request.status);
177424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  };
1781e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1791e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  chrome.identity.getAuthToken({interactive: false}, function(token) {
1801e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    if (token) {
1811e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      request.setRequestHeader('Authorization', 'Bearer ' + token);
1821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      request.send(JSON.stringify(errorObject));
1831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    }
1841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  });
185424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)}
186558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch
187424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)// Limiting 1 error report per background page load.
188424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)var errorReported = false;
189424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
190424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)/**
191424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * Reports an error to the server and the user, as appropriate.
192424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * @param {Error} error Error to report.
193424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) */
194424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)function reportError(error) {
195424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var message = 'Critical error:\n' + error.stack;
1965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (isInDebugMode())
1975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    console.error(message);
1985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
199424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  if (!errorReported) {
200424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    errorReported = true;
201424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    chrome.metricsPrivate.getIsCrashReportingEnabled(function(isEnabled) {
202424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if (isEnabled)
203424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        sendErrorReport(error);
2045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if (isInDebugMode())
205424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        alert(message);
206424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    });
207424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  }
208424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)}
2092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
210424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)// Partial mirror of chrome.* for all instrumented functions.
211424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)var instrumented = {};
212424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
213424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)/**
214424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * Wrapper plugin. These plugins extend instrumentation added by
215424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * wrapper.wrapCallback by adding code that executes before and after the call
216424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * to the original callback provided by the extension.
217424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *
218424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * @typedef {{
219424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *   prologue: function (),
220424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *   epilogue: function ()
221424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * }}
222424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) */
223424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)var WrapperPlugin;
224424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
225424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)/**
226424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * Wrapper for callbacks. Used to add error handling and other services to
227424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * callbacks for HTML and Chrome functions and events.
228424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) */
229424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)var wrapper = (function() {
2302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  /**
231424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Factory for wrapper plugins. If specified, it's used to generate an
232424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * instance of WrapperPlugin each time we wrap a callback (which corresponds
233424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * to addListener call for Chrome events, and to every API call that specifies
234424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * a callback). WrapperPlugin's lifetime ends when the callback for which it
235424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * was generated, exits. It's possible to have several instances of
236424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * WrapperPlugin at the same time.
237424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * An instance of WrapperPlugin can have state that can be shared by its
238424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * constructor, prologue() and epilogue(). Also WrapperPlugins can change
239424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * state of other objects, for example, to do refcounting.
240a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)   * @type {?function(): WrapperPlugin}
241a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   */
242424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var wrapperPluginFactory = null;
243a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
244a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  /**
245424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Registers a wrapper plugin factory.
246424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @param {function(): WrapperPlugin} factory Wrapper plugin factory.
247a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   */
248424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  function registerWrapperPluginFactory(factory) {
249424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (wrapperPluginFactory) {
2503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      reportError(buildErrorWithMessageForServer(
251424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)          'registerWrapperPluginFactory: factory is already registered.'));
252a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    }
253558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch
254424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    wrapperPluginFactory = factory;
2552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
2562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  /**
258424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * True if currently executed code runs in a callback or event handler that
259424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * was instrumented by wrapper.wrapCallback() call.
260424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @type {boolean}
2612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)   */
262424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var isInWrappedCallback = false;
2632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  /**
265424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Required callbacks that are not yet called. Includes both task and non-task
266424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * callbacks. This is a map from unique callback id to the stack at the moment
267424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * when the callback was wrapped. This stack identifies the callback.
268424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Used only for diagnostics.
269424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @type {Object.<number, string>}
2702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)   */
271424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var pendingCallbacks = {};
2722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  /**
274424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Unique ID of the next callback.
275424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @type {number}
2762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)   */
277424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var nextCallbackId = 0;
27890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
27990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  /**
280424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Gets diagnostic string with the status of the wrapper.
281424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @return {string} Diagnostic string.
28290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   */
283424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  function debugGetStateString() {
284f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return 'pendingCallbacks @' + Date.now() + ' = ' +
285f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        JSON.stringify(pendingCallbacks);
28690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
287c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
288c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  /**
289424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Checks that we run in a wrapped callback.
2903551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)   */
291424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  function checkInWrappedCallback() {
292424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (!isInWrappedCallback) {
293424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      reportError(buildErrorWithMessageForServer(
294424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)          'Not in instrumented callback'));
2953551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    }
2963551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  }
2973551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
2983551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  /**
299c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)   * Adds error processing to an API callback.
300c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)   * @param {Function} callback Callback to instrument.
301424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @param {boolean=} opt_isEventListener True if the callback is a listener to
302424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   *     a Chrome API event.
303c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)   * @return {Function} Instrumented callback.
304c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)   */
305a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  function wrapCallback(callback, opt_isEventListener) {
306558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    var callbackId = nextCallbackId++;
307424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
308a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    if (!opt_isEventListener) {
309424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      checkInWrappedCallback();
310f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      pendingCallbacks[callbackId] = new Error().stack + ' @' + Date.now();
311a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    }
312558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch
313424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // wrapperPluginFactory may be null before task manager is built, and in
314424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // tests.
315424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    var wrapperPluginInstance = wrapperPluginFactory && wrapperPluginFactory();
316424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
317c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return function() {
318c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      // This is the wrapper for the callback.
319c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      try {
320424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        verify(!isInWrappedCallback, 'Re-entering instrumented callback');
321424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        isInWrappedCallback = true;
322bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch
323a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)        if (!opt_isEventListener)
324558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch          delete pendingCallbacks[callbackId];
325558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch
326424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        if (wrapperPluginInstance)
327424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)          wrapperPluginInstance.prologue();
328424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
329558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch        // Call the original callback.
330a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        var returnValue = callback.apply(null, arguments);
331558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch
332424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        if (wrapperPluginInstance)
333424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)          wrapperPluginInstance.epilogue();
334bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch
335424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        verify(isInWrappedCallback,
336bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch               'Instrumented callback is not instrumented upon exit');
337424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        isInWrappedCallback = false;
338a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
339a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        return returnValue;
340c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      } catch (error) {
3413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        reportError(error);
342c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      }
343c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    };
344c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  }
345c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
346c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  /**
347ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch   * Returns an instrumented function.
348424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @param {!Array.<string>} functionIdentifierParts Path to the chrome.*
349424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   *     function.
350ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch   * @param {string} functionName Name of the chrome API function.
351c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)   * @param {number} callbackParameter Index of the callback parameter to this
352c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)   *     API function.
353424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @return {Function} An instrumented function.
354c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)   */
355ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch  function createInstrumentedFunction(
356ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      functionIdentifierParts,
357ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      functionName,
358ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      callbackParameter) {
359ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    return function() {
360c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      // This is the wrapper for the API function. Pass the wrapped callback to
361c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      // the original function.
362c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      var callback = arguments[callbackParameter];
363c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      if (typeof callback != 'function') {
3643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        reportError(buildErrorWithMessageForServer(
3653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            'Argument ' + callbackParameter + ' of ' +
3663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            functionIdentifierParts.join('.') + '.' + functionName +
3673551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            ' is not a function'));
368c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      }
369558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch      arguments[callbackParameter] = wrapCallback(
370558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch          callback, functionName == 'addListener');
371ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
372ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      var chromeContainer = chrome;
373424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      functionIdentifierParts.forEach(function(fragment) {
374ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        chromeContainer = chromeContainer[fragment];
375ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      });
376ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      return chromeContainer[functionName].
377ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch          apply(chromeContainer, arguments);
378c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    };
379c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  }
380c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
381ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch  /**
382ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch   * Instruments an API function to add error processing to its user
383ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch   * code-provided callback.
384ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch   * @param {string} functionIdentifier Full identifier of the function without
385ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch   *     the 'chrome.' portion.
386ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch   * @param {number} callbackParameter Index of the callback parameter to this
387ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch   *     API function.
388ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch   */
389ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch  function instrumentChromeApiFunction(functionIdentifier, callbackParameter) {
390ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    var functionIdentifierParts = functionIdentifier.split('.');
391ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    var functionName = functionIdentifierParts.pop();
392ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    var chromeContainer = chrome;
393ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    var instrumentedContainer = instrumented;
394424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    functionIdentifierParts.forEach(function(fragment) {
395ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      chromeContainer = chromeContainer[fragment];
3963551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      if (!chromeContainer) {
3973551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        reportError(buildErrorWithMessageForServer(
3983551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            'Cannot instrument ' + functionIdentifier));
3993551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      }
4003551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
401ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      if (!(fragment in instrumentedContainer))
402ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        instrumentedContainer[fragment] = {};
403ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
404ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      instrumentedContainer = instrumentedContainer[fragment];
405ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    });
406ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
407ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    var targetFunction = chromeContainer[functionName];
4083551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    if (!targetFunction) {
4093551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      reportError(buildErrorWithMessageForServer(
4103551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          'Cannot instrument ' + functionIdentifier));
4113551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    }
412ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
413ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    instrumentedContainer[functionName] = createInstrumentedFunction(
414ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        functionIdentifierParts,
415ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        functionName,
416ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        callbackParameter);
417ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch  }
418ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
419ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch  instrumentChromeApiFunction('runtime.onSuspend.addListener', 0);
420c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
421424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  instrumented.runtime.onSuspend.addListener(function() {
422558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    var stringifiedPendingCallbacks = JSON.stringify(pendingCallbacks);
423c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    verify(
424424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        stringifiedPendingCallbacks == '{}',
425f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        'Pending callbacks when unloading event page @' + Date.now() + ':' +
426424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        stringifiedPendingCallbacks);
4272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  });
4282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
4292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return {
430424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    wrapCallback: wrapCallback,
431ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    instrumentChromeApiFunction: instrumentChromeApiFunction,
432424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    registerWrapperPluginFactory: registerWrapperPluginFactory,
433424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    checkInWrappedCallback: checkInWrappedCallback,
434424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    debugGetStateString: debugGetStateString
435424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  };
436424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)})();
437424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
438424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)wrapper.instrumentChromeApiFunction('alarms.get', 1);
439424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)wrapper.instrumentChromeApiFunction('alarms.onAlarm.addListener', 0);
440424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)wrapper.instrumentChromeApiFunction('identity.getAuthToken', 1);
4411e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)wrapper.instrumentChromeApiFunction('identity.onSignInChanged.addListener', 0);
442424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)wrapper.instrumentChromeApiFunction('identity.removeCachedAuthToken', 1);
443a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)wrapper.instrumentChromeApiFunction('storage.local.get', 1);
4441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)wrapper.instrumentChromeApiFunction('webstorePrivate.getBrowserLogin', 0);
445424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
446424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)/**
447a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Promise adapter for all JS promises to the task manager.
4485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */
449a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)function registerPromiseAdapter() {
4505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  var originalThen = Promise.prototype.then;
451a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  var originalCatch = Promise.prototype.catch;
452a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
453a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  /**
454a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * Takes a promise and adds the callback tracker to it.
455a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * @param {object} promise Promise that receives the callback tracker.
456a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   */
457a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  function instrumentPromise(promise) {
458a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (promise.__tracker === undefined) {
459a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      promise.__tracker = createPromiseCallbackTracker(promise);
460a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
4615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
462a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
463a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  Promise.prototype.then = function(onResolved, onRejected) {
464a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    instrumentPromise(this);
465a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return this.__tracker.handleThen(onResolved, onRejected);
4665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  };
467a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
468a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  Promise.prototype.catch = function(onRejected) {
469a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    instrumentPromise(this);
470a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return this.__tracker.handleCatch(onRejected);
4715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  };
472a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
473a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  /**
474a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * Promise Callback Tracker.
475a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * Handles coordination of 'then' and 'catch' callbacks in a task
476a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * manager compatible way. For an individual promise, either the 'then'
477a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * arguments or the 'catch' arguments will be processed, never both.
478a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *
479a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * Example:
480a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     var p = new Promise([Function]);
481a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     p.then([ThenA]);
482a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     p.then([ThenB]);
483a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     p.catch([CatchA]);
484a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     On resolution, [ThenA] and [ThenB] will be used. [CatchA] is discarded.
485a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     On rejection, vice versa.
486a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *
487a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * Clarification:
488a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     Chained promises create a new promise that is tracked separately from
489a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     the originaing promise, as the example below demonstrates:
490a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *
491a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     var p = new Promise([Function]));
492a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     p.then([ThenA]).then([ThenB]).catch([CatchA]);
493a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *         ^             ^             ^
494a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *         |             |             + Returns a new promise.
495a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *         |             + Returns a new promise.
496a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *         + Returns a new promise.
497a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *
498a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     Four promises exist in the above statement, each with its own
499a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     resolution and rejection state. However, by default, this state is
500a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     chained to the previous promise's resolution or rejection
501a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     state.
502a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *
503a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     If p resolves, then the 'then' calls will execute until all the 'then'
504a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     clauses are executed. If the result of either [ThenA] or [ThenB] is a
505a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     promise, then that execution state will guide the remaining chain.
506a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     Similarly, if [CatchA] returns a promise, it can also guide the
507a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     remaining chain. In this specific case, the chain ends, so there
508a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     is nothing left to do.
509a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * @param {object} promise Promise being tracked.
510a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * @return {object} A promise callback tracker.
511a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   */
512a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  function createPromiseCallbackTracker(promise) {
513a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    /**
514a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * Callback Tracker. Holds an array of callbacks created for this promise.
515a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * The indirection allows quick checks against the array and clearing the
516a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * array without ugly splicing and copying.
517a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @typedef {{
518a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     *   callback: array.<Function>=
519a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * }}
520a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     */
521a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    var CallbackTracker;
522a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
523a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    /** @type {CallbackTracker} */
524a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    var thenTracker = {callbacks: []};
525a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    /** @type {CallbackTracker} */
526a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    var catchTracker = {callbacks: []};
527a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
528a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    /**
529a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * Returns true if the specified value is callable.
530a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @param {*} value Value to check.
531a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @return {boolean} True if the value is a callable.
532a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     */
533a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    function isCallable(value) {
534a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return typeof value === 'function';
535a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
536a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
537a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    /**
538a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * Takes a tracker and clears its callbacks in a manner consistent with
539a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * the task manager. For the task manager, it also calls all callbacks
540a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * by no-oping them first and then calling them.
541a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @param {CallbackTracker} tracker Tracker to clear.
542a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     */
543a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    function clearTracker(tracker) {
544a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      if (tracker.callbacks) {
545a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        var callbacksToClear = tracker.callbacks;
546a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        // No-ops all callbacks of this type.
547a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        tracker.callbacks = undefined;
548a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        // Do not wrap the promise then argument!
549a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        // It will call wrapped callbacks.
550a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        originalThen.call(Promise.resolve(), function() {
551a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          for (var i = 0; i < callbacksToClear.length; i++) {
552a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            callbacksToClear[i]();
553a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          }
554a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        });
555a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      }
556a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
557a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
558a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    /**
559a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * Takes the argument to a 'then' or 'catch' function and applies
560a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * a wrapping to callables consistent to ECMA promises.
561a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @param {*} maybeCallback Argument to 'then' or 'catch'.
562a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @param {CallbackTracker} sameTracker Tracker for the call type.
563a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     *     Example: If the argument is from a 'then' call, use thenTracker.
564a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @param {CallbackTracker} otherTracker Tracker for the opposing call type.
565a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     *     Example: If the argument is from a 'then' call, use catchTracker.
566a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @return {*} Consumable argument with necessary wrapping applied.
567a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     */
568a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    function registerAndWrapMaybeCallback(
569a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          maybeCallback, sameTracker, otherTracker) {
570a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      // If sameTracker.callbacks is undefined, we've reached an ending state
571a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      // that means this callback will never be called back.
572a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      // We will still forward this call on to let the promise system
573a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      // handle further processing, but since this promise is in an ending state
574a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      // we can be confident it will never be called back.
575c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      if (isCallable(maybeCallback) &&
576c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch          !maybeCallback.wrappedByPromiseTracker &&
577c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch          sameTracker.callbacks) {
578a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        var handler = wrapper.wrapCallback(function() {
579a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          if (sameTracker.callbacks) {
580a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            clearTracker(otherTracker);
581a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            return maybeCallback.apply(null, arguments);
582a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          }
583a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        }, false);
584c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        // Harmony promises' catch calls will call into handleThen,
585c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        // double-wrapping all catch callbacks. Regular promise catch calls do
586c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        // not call into handleThen. Setting an attribute on the wrapped
587c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        // function is compatible with both promise implementations.
588c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        handler.wrappedByPromiseTracker = true;
589a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        sameTracker.callbacks.push(handler);
590a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        return handler;
591a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      } else {
592a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        return maybeCallback;
593a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      }
594a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
595a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
596a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    /**
597a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * Tracks then calls equivalent to Promise.prototype.then.
598a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @param {*} onResolved Argument to use if the promise is resolved.
599a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @param {*} onRejected Argument to use if the promise is rejected.
600a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @return {object} Promise resulting from the 'then' call.
601a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     */
602a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    function handleThen(onResolved, onRejected) {
603a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      var resolutionHandler =
604a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          registerAndWrapMaybeCallback(onResolved, thenTracker, catchTracker);
605a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      var rejectionHandler =
606a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          registerAndWrapMaybeCallback(onRejected, catchTracker, thenTracker);
607a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return originalThen.call(promise, resolutionHandler, rejectionHandler);
608a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
609a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
610a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    /**
611a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * Tracks then calls equivalent to Promise.prototype.catch.
612a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @param {*} onRejected Argument to use if the promise is rejected.
613a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     * @return {object} Promise resulting from the 'catch' call.
614a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)     */
615a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    function handleCatch(onRejected) {
616a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      var rejectionHandler =
617a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          registerAndWrapMaybeCallback(onRejected, catchTracker, thenTracker);
618a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return originalCatch.call(promise, rejectionHandler);
619a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
620a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
621c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    // Register at least one resolve and reject callback so we always receive
622c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    // a callback to update the task manager and clear the callbacks
623c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    // that will never occur.
624c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    //
625c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    // The then form is used to avoid reentrancy by handleCatch,
626c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    // which ends up calling handleThen.
627c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    handleThen(function() {}, function() {});
628a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
629a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return {
630a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      handleThen: handleThen,
631a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      handleCatch: handleCatch
632a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    };
633a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
634a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
635a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
636a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)registerPromiseAdapter();
637a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
638a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)/**
639a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Control promise rejection.
640a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @enum {number}
641a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */
642a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)var PromiseRejection = {
643a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  /** Disallow promise rejection */
644a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DISALLOW: 0,
645a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  /** Allow promise rejection */
646a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  ALLOW: 1
647a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
648a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
649a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)/**
650a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Provides the promise equivalent of instrumented.storage.local.get.
651a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param {Object} defaultStorageObject Default storage object to fill.
652a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param {PromiseRejection=} opt_allowPromiseRejection If
653a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) *     PromiseRejection.ALLOW, allow promise rejection on errors, otherwise the
654a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) *     default storage object is resolved.
655a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @return {Promise} A promise that fills the default storage object. On
656a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) *     failure, if promise rejection is allowed, the promise is rejected,
657a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) *     otherwise it is resolved to the default storage object.
658a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */
659a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)function fillFromChromeLocalStorage(
660a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    defaultStorageObject,
661a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    opt_allowPromiseRejection) {
662a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return new Promise(function(resolve, reject) {
663effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    // We have to create a keys array because keys with a default value
664effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    // of undefined will cause that key to not be looked up!
665effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    var keysToGet = [];
666effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    for (var key in defaultStorageObject) {
667effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      keysToGet.push(key);
668effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    }
669effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    instrumented.storage.local.get(keysToGet, function(items) {
670a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      if (items) {
671effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        // Merge the result with the default storage object to ensure all keys
672effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        // requested have either the default value or the retrieved storage
673effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        // value.
674effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        var result = {};
675effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        for (var key in defaultStorageObject) {
676effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          result[key] = (key in items) ? items[key] : defaultStorageObject[key];
677effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        }
678effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        resolve(result);
679a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      } else if (opt_allowPromiseRejection === PromiseRejection.ALLOW) {
680a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        reject();
681a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      } else {
682a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        resolve(defaultStorageObject);
683a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      }
684a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    });
685a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  });
686a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
6875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
6885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)/**
689424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * Builds the object to manage tasks (mutually exclusive chains of events).
690424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * @param {function(string, string): boolean} areConflicting Function that
691424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     checks if a new task can't be added to a task queue that contains an
692424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *     existing task.
693424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) * @return {Object} Task manager interface.
694424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) */
695424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)function buildTaskManager(areConflicting) {
696424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  /**
697424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Queue of scheduled tasks. The first element, if present, corresponds to the
698424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * currently running task.
6994e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)   * @type {Array.<Object.<string, function()>>}
700424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   */
701424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var queue = [];
702424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
703424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  /**
704424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Count of unfinished callbacks of the current task.
705424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @type {number}
706424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   */
707424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var taskPendingCallbackCount = 0;
708424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
709424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  /**
710424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * True if currently executed code is a part of a task.
711424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @type {boolean}
712424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   */
713424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  var isInTask = false;
714424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
715424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  /**
716424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Starts the first queued task.
717424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   */
718424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  function startFirst() {
719424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    verify(queue.length >= 1, 'startFirst: queue is empty');
720424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    verify(!isInTask, 'startFirst: already in task');
721424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    isInTask = true;
722424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
723424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    // Start the oldest queued task, but don't remove it from the queue.
724424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    verify(
725424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        taskPendingCallbackCount == 0,
726424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        'tasks.startFirst: still have pending task callbacks: ' +
727424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        taskPendingCallbackCount +
728424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        ', queue = ' + JSON.stringify(queue) + ', ' +
729424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        wrapper.debugGetStateString());
730424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    var entry = queue[0];
731424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    console.log('Starting task ' + entry.name);
732424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
7334e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    entry.task();
734424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
735424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    verify(isInTask, 'startFirst: not in task at exit');
736424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    isInTask = false;
737424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (taskPendingCallbackCount == 0)
738424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      finish();
739424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  }
740424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
741424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  /**
742424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Checks if a new task can be added to the task queue.
743424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @param {string} taskName Name of the new task.
744424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @return {boolean} Whether the new task can be added.
745424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   */
746424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  function canQueue(taskName) {
747424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    for (var i = 0; i < queue.length; ++i) {
748424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if (areConflicting(taskName, queue[i].name)) {
749424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        console.log('Conflict: new=' + taskName +
750424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                    ', scheduled=' + queue[i].name);
751424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        return false;
752424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      }
753424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    }
754424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
755424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return true;
756424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  }
757424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
758424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  /**
759424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Adds a new task. If another task is not running, runs the task immediately.
760424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * If any task in the queue is not compatible with the task, ignores the new
761424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * task. Otherwise, stores the task for future execution.
762424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @param {string} taskName Name of the task.
7634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)   * @param {function()} task Function to run.
764424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   */
765424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  function add(taskName, task) {
766424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    wrapper.checkInWrappedCallback();
767424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    console.log('Adding task ' + taskName);
768424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (!canQueue(taskName))
769424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return;
770424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
771424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    queue.push({name: taskName, task: task});
772424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
773424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (queue.length == 1) {
774424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      startFirst();
775424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    }
776424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  }
777424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
778424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  /**
779424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Completes the current task and starts the next queued task if available.
780424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   */
781424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  function finish() {
782424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    verify(queue.length >= 1,
783424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)           'tasks.finish: The task queue is empty');
784424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    console.log('Finishing task ' + queue[0].name);
785424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    queue.shift();
786424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
787424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (queue.length >= 1)
788424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      startFirst();
789424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  }
790424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
791424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  instrumented.runtime.onSuspend.addListener(function() {
792424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    verify(
793424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        queue.length == 0,
794424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        'Incomplete task when unloading event page,' +
795424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        ' queue = ' + JSON.stringify(queue) + ', ' +
796424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        wrapper.debugGetStateString());
797424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  });
798424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
799424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
800424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  /**
801424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * Wrapper plugin for tasks.
802424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * @constructor
803424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   */
804424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  function TasksWrapperPlugin() {
805424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    this.isTaskCallback = isInTask;
806424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (this.isTaskCallback)
807424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      ++taskPendingCallbackCount;
808424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  }
809424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
810424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  TasksWrapperPlugin.prototype = {
811424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    /**
812424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)     * Plugin code to be executed before invoking the original callback.
813424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)     */
814424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    prologue: function() {
815424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if (this.isTaskCallback) {
816424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        verify(!isInTask, 'TasksWrapperPlugin.prologue: already in task');
817424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        isInTask = true;
818424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      }
819424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    },
820424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
821424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    /**
822424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)     * Plugin code to be executed after invoking the original callback.
823424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)     */
824424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    epilogue: function() {
825424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if (this.isTaskCallback) {
826424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        verify(isInTask, 'TasksWrapperPlugin.epilogue: not in task at exit');
827424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        isInTask = false;
828424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        if (--taskPendingCallbackCount == 0)
829424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)          finish();
830424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      }
831424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    }
832424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  };
833424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
834424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  wrapper.registerWrapperPluginFactory(function() {
835424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return new TasksWrapperPlugin();
836424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  });
837424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
838424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  return {
8394e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    add: add
8402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  };
8412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
84290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
84390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)/**
84490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * Builds an object to manage retrying activities with exponential backoff.
84590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {string} name Name of this attempt manager.
84690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {function()} attempt Activity that the manager retries until it
84790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) *     calls 'stop' method.
84890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {number} initialDelaySeconds Default first delay until first retry.
84990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @param {number} maximumDelaySeconds Maximum delay between retries.
85090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * @return {Object} Attempt manager interface.
85190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) */
85290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)function buildAttemptManager(
85390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    name, attempt, initialDelaySeconds, maximumDelaySeconds) {
854558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch  var alarmName = 'attempt-scheduler-' + name;
855558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch  var currentDelayStorageKey = 'current-delay-' + name;
85690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
85790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  /**
85890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   * Creates an alarm for the next attempt. The alarm is repeating for the case
85990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   * when the next attempt crashes before registering next alarm.
86090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   * @param {number} delaySeconds Delay until next retry.
86190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   */
86290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  function createAlarm(delaySeconds) {
86390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    var alarmInfo = {
86490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      delayInMinutes: delaySeconds / 60,
86590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      periodInMinutes: maximumDelaySeconds / 60
86690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    };
86790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    chrome.alarms.create(alarmName, alarmInfo);
86890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
86990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
87090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  /**
871a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   * Indicates if this attempt manager has started.
872a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   * @param {function(boolean)} callback The function's boolean parameter is
873a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   *     true if the attempt manager has started, false otherwise.
874a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   */
875a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  function isRunning(callback) {
876ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    instrumented.alarms.get(alarmName, function(alarmInfo) {
877a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      callback(!!alarmInfo);
878a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    });
879a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  }
880a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
881a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  /**
882010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)   * Schedules the alarm with a random factor to reduce the chance that all
883010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)   * clients will fire their timers at the same time.
884010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)   * @param {number} durationSeconds Number of seconds before firing the alarm.
88590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   */
886010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  function scheduleAlarm(durationSeconds) {
8875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    durationSeconds = Math.min(durationSeconds, maximumDelaySeconds);
8885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    var randomizedRetryDuration = durationSeconds * (1 + 0.2 * Math.random());
88990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
890010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    createAlarm(randomizedRetryDuration);
89190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
89290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    var items = {};
893010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    items[currentDelayStorageKey] = randomizedRetryDuration;
894ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    chrome.storage.local.set(items);
89590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
89690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
89790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  /**
89890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   * Starts repeated attempts.
89990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   * @param {number=} opt_firstDelaySeconds Time until the first attempt, if
90090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   *     specified. Otherwise, initialDelaySeconds will be used for the first
90190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   *     attempt.
90290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   */
90390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  function start(opt_firstDelaySeconds) {
90490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    if (opt_firstDelaySeconds) {
90590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      createAlarm(opt_firstDelaySeconds);
906ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch      chrome.storage.local.remove(currentDelayStorageKey);
90790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    } else {
908010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      scheduleAlarm(initialDelaySeconds);
90990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    }
91090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
91190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
91290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  /**
91390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   * Stops repeated attempts.
91490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   */
91590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  function stop() {
91690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    chrome.alarms.clear(alarmName);
917ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    chrome.storage.local.remove(currentDelayStorageKey);
91890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
91990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
92090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  /**
921010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)   * Schedules an exponential backoff retry.
922010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)   * @return {Promise} A promise to schedule the retry.
92390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)   */
924010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  function scheduleRetry() {
925a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    var request = {};
926a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    request[currentDelayStorageKey] = undefined;
927010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    return fillFromChromeLocalStorage(request, PromiseRejection.ALLOW)
928a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        .catch(function() {
929a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          request[currentDelayStorageKey] = maximumDelaySeconds;
930a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          return Promise.resolve(request);
931010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)        })
932010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)        .then(function(items) {
933010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)          console.log('scheduleRetry-get-storage ' + JSON.stringify(items));
934010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)          var retrySeconds = initialDelaySeconds;
935010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)          if (items[currentDelayStorageKey]) {
936010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)            retrySeconds = items[currentDelayStorageKey] * 2;
937010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)          }
938010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)          scheduleAlarm(retrySeconds);
939a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        });
94090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
94190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
942ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch  instrumented.alarms.onAlarm.addListener(function(alarm) {
94390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    if (alarm.name == alarmName)
944a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      isRunning(function(running) {
945a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)        if (running)
946a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)          attempt();
947a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      });
94890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  });
94990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
95090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  return {
95190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    start: start,
952010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    scheduleRetry: scheduleRetry,
953a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    stop: stop,
954a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    isRunning: isRunning
955a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  };
956a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)}
957a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
9581e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)// TODO(robliao): Use signed-in state change watch API when it's available.
959a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)/**
960a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * Wraps chrome.identity to provide limited listening support for
961a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * the sign in state by polling periodically for the auth token.
962a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) * @return {Object} The Authentication Manager interface.
963a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) */
964a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)function buildAuthenticationManager() {
965a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  var alarmName = 'sign-in-alarm';
966a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
967a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  /**
9681e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)   * Gets an OAuth2 access token.
969a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * @return {Promise} A promise to get the authentication token. If there is
970a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     no token, the request is rejected.
971a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   */
972a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  function getAuthToken() {
973a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return new Promise(function(resolve, reject) {
974a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      instrumented.identity.getAuthToken({interactive: false}, function(token) {
975a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        if (chrome.runtime.lastError || !token) {
976a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          reject();
977a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        } else {
978a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          resolve(token);
979a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        }
980a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      });
9811e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    });
9821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  }
9831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
9841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  /**
9851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)   * Determines whether there is an account attached to the profile.
986a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * @return {Promise} A promise to determine if there is an account attached
987a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   *     to the profile.
9881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)   */
989a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  function isSignedIn() {
990a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return new Promise(function(resolve) {
991a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      instrumented.webstorePrivate.getBrowserLogin(function(accountInfo) {
992a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        resolve(!!accountInfo.login);
993a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      });
994a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    });
995a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  }
996a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
997a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  /**
998a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   * Removes the specified cached token.
999a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   * @param {string} token Authentication Token to remove from the cache.
1000a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)   * @return {Promise} A promise that resolves on completion.
1001a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   */
1002a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  function removeToken(token) {
1003a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return new Promise(function(resolve) {
1004a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      instrumented.identity.removeCachedAuthToken({token: token}, function() {
1005a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        // Let Chrome know about a possible problem with the token.
1006a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        getAuthToken();
1007a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        resolve();
1008a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      });
1009a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    });
1010a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  }
1011a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
1012a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  var listeners = [];
1013a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
1014a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  /**
1015a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   * Registers a listener that gets called back when the signed in state
1016a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   * is found to be changed.
1017a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)   * @param {function()} callback Called when the answer to isSignedIn changes.
1018a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)   */
1019a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  function addListener(callback) {
1020a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    listeners.push(callback);
1021a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  }
1022a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
1023424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  /**
10241e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)   * Checks if the last signed in state matches the current one.
1025424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   * If it doesn't, it notifies the listeners of the change.
1026424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)   */
10271e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  function checkAndNotifyListeners() {
1028a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    isSignedIn().then(function(signedIn) {
1029a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      fillFromChromeLocalStorage({lastSignedInState: undefined})
1030a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          .then(function(items) {
1031a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            if (items.lastSignedInState != signedIn) {
1032a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              chrome.storage.local.set(
1033a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                  {lastSignedInState: signedIn});
1034a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              listeners.forEach(function(callback) {
1035a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                callback();
1036a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              });
1037a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            }
1038a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        });
10391e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      });
1040a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  }
1041a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
10421e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  instrumented.identity.onSignInChanged.addListener(function() {
10431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    checkAndNotifyListeners();
10441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  });
10451e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1046ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch  instrumented.alarms.onAlarm.addListener(function(alarm) {
1047a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    if (alarm.name == alarmName)
10481e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      checkAndNotifyListeners();
1049a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  });
1050a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
1051a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // Poll for the sign in state every hour.
1052a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // One hour is just an arbitrary amount of time chosen.
1053a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  chrome.alarms.create(alarmName, {periodInMinutes: 60});
1054a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
1055a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  return {
1056a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    addListener: addListener,
10571e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    getAuthToken: getAuthToken,
1058a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    isSignedIn: isSignedIn,
1059a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    removeToken: removeToken
106090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  };
106190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
1062