text: /*
   * Copyright (C) 2011 Google Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   *
   *     * Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above
   * copyright notice, this list of conditions and the following disclaimer
   * in the documentation and/or other materials provided with the
   * distribution.
   *     * Neither the name of Google Inc. nor the names of its
   * contributors may be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  /**
   * @constructor
   * @extends {WebInspector.SDKObject}
   * @param {!WebInspector.Target} target
   */
  WebInspector.ServiceWorkerManager = function(target)
  {
      WebInspector.SDKObject.call(this, target);
      target.registerServiceWorkerDispatcher(new WebInspector.ServiceWorkerDispatcher(this));
      this._lastAnonymousTargetId = 0;
      this._agent = target.serviceWorkerAgent();
      /** @type {!Map.<string, !WebInspector.ServiceWorker>} */
      this._workers = new Map();
      /** @type {!Map.<string, !WebInspector.ServiceWorkerRegistration>} */
      this._registrations = new Map();
      this.enable();
  }
  
  WebInspector.ServiceWorkerManager.Events = {
      WorkersUpdated: "WorkersUpdated",
      RegistrationUpdated: "RegistrationUpdated",
      RegistrationDeleted: "RegistrationDeleted",
      DebugOnStartUpdated: "DebugOnStartUpdated"
  }
  
  WebInspector.ServiceWorkerManager.prototype = {
      enable: function()
      {
          if (this._enabled)
              return;
          this._enabled = true;
  
          this._agent.enable();
          WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.MainFrameNavigated, this._mainFrameNavigated, this);
      },
  
      disable: function()
      {
          if (!this._enabled)
              return;
          this._enabled = false;
  
          for (var worker of this._workers.values())
              worker._connection.close();
          this._workers.clear();
          this._registrations.clear();
          this._agent.disable();
          WebInspector.targetManager.removeEventListener(WebInspector.TargetManager.Events.MainFrameNavigated, this._mainFrameNavigated, this);
      },
  
      /**
       * @return {!Iterable.<!WebInspector.ServiceWorker>}
       */
      workers: function()
      {
          return this._workers.values();
      },
  
      /**
       * @return {boolean}
       */
      hasWorkers: function()
      {
          return !!this._workers.size;
      },
  
      /**
       * @return {boolean}
       */
      debugOnStart: function()
      {
          return !!this._debugOnStart;
      },
  
      /**
       * @param {boolean} flag
       */
      setDebugOnStart: function(flag)
      {
          this._agent.setDebugOnStart(flag);
      },
  
      /**
       * @param {string} registrationId
       * @param {boolean} flag
       */
      setForceUpdateOnPageLoad: function(registrationId, flag)
      {
          this._agent.setForceUpdateOnPageLoad(registrationId, flag);
      },
  
      /**
       * @return {!Map.<string, !WebInspector.ServiceWorkerRegistration>}
       */
      registrations: function()
      {
          return this._registrations;
      },
  
      /**
       * @param {string} versionId
       * @return {?WebInspector.ServiceWorkerVersion}
       */
      findVersion: function(versionId)
      {
          for (var registration of this.registrations().values()) {
             var version = registration.versions.get(versionId);
             if (version)
                return version;
          }
          return null;
      },
  
      /**
       * @param {string} registrationId
       */
      deleteRegistration: function(registrationId)
      {
          var registration = this._registrations.get(registrationId);
          if (!registration)
              return;
          if (registration._isRedundant()) {
              this._registrations.delete(registrationId);
              this.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.RegistrationDeleted, registration);
              return;
          }
          registration._deleting = true;
          for (var version of registration.versions.values())
              this.stopWorker(version.id);
          this._unregister(registration.scopeURL);
      },
  
      /**
       * @param {string} registrationId
       */
      updateRegistration: function(registrationId)
      {
          var registration = this._registrations.get(registrationId);
          if (!registration)
              return;
          this._agent.updateRegistration(registration.scopeURL);
      },
  
       /**
        * @param {string} registrationId
        * @param {string} data
        */
      deliverPushMessage: function(registrationId, data)
      {
          var registration = this._registrations.get(registrationId);
          if (!registration)
              return;
          var origin = WebInspector.ParsedURL.splitURLIntoPathComponents(registration.scopeURL)[0];
          this._agent.deliverPushMessage(origin, registrationId, data);
      },
  
      /**
       * @param {!ServiceWorkerAgent.TargetID} targetId
       */
      activateTarget: function(targetId)
      {
          this._agent.activateTarget(targetId);
      },
  
      /**
       * @param {string} scope
       */
      _unregister: function(scope)
      {
          this._agent.unregister(scope);
      },
  
      /**
       * @param {string} scope
       */
      startWorker: function(scope)
      {
          this._agent.startWorker(scope);
      },
  
      /**
       * @param {string} versionId
       */
      stopWorker: function(versionId)
      {
          this._agent.stopWorker(versionId);
      },
  
      /**
       * @param {string} versionId
       */
      inspectWorker: function(versionId)
      {
          this._agent.inspectWorker(versionId);
      },
  
      /**
       * @param {string} versionId
       */
      skipWaiting: function(versionId)
      {
          this._agent.skipWaiting(versionId);
      },
  
      /**
       * @param {!ServiceWorkerAgent.TargetID} targetId
       * @param {function(?WebInspector.TargetInfo)=} callback
       */
      getTargetInfo: function(targetId, callback)
      {
          /**
           * @param {?Protocol.Error} error
           * @param {?ServiceWorkerAgent.TargetInfo} targetInfo
           */
          function innerCallback(error, targetInfo)
          {
              if (error) {
                  console.error(error);
                  callback(null);
                  return;
              }
              if (targetInfo)
                  callback(new WebInspector.TargetInfo(targetInfo));
              else
                  callback(null)
          }
          this._agent.getTargetInfo(targetId, innerCallback);
      },
  
      /**
       * @param {string} workerId
       * @param {string} url
       * @param {string} versionId
       */
      _workerCreated: function(workerId, url, versionId)
      {
          new WebInspector.ServiceWorker(this, workerId, url, versionId);
      },
  
      /**
       * @param {string} workerId
       */
      _workerTerminated: function(workerId)
      {
          var worker = this._workers.get(workerId);
          if (!worker)
              return;
  
          worker._closeConnection();
          this._workers.delete(workerId);
  
          this.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.WorkersUpdated);
      },
  
      /**
       * @param {string} workerId
       * @param {string} message
       */
      _dispatchMessage: function(workerId, message)
      {
          var worker = this._workers.get(workerId);
          if (worker)
              worker._connection.dispatch(message);
      },
  
      /**
       * @param {!Array.<!ServiceWorkerAgent.ServiceWorkerRegistration>} registrations
       */
      _workerRegistrationUpdated: function(registrations)
      {
          for (var payload of registrations) {
              var registration = this._registrations.get(payload.registrationId);
              if (!registration) {
                  registration = new WebInspector.ServiceWorkerRegistration(payload);
                  this._registrations.set(payload.registrationId, registration);
                  this.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.RegistrationUpdated, registration);
                  continue;
              }
              registration._update(payload);
  
              if (registration._shouldBeRemoved()) {
                  this._registrations.delete(registration.id);
                  this.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.RegistrationDeleted, registration);
              } else {
                  this.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.RegistrationUpdated, registration);
              }
          }
      },
  
      /**
       * @param {!Array.<!ServiceWorkerAgent.ServiceWorkerVersion>} versions
       */
      _workerVersionUpdated: function(versions)
      {
          /** @type {!Set.<!WebInspector.ServiceWorkerRegistration>} */
          var registrations = new Set();
          for (var payload of versions) {
              var registration = this._registrations.get(payload.registrationId);
              if (!registration)
                  continue;
              registration._updateVersion(payload);
              registrations.add(registration);
          }
          for (var registration of registrations) {
              if (registration._shouldBeRemoved()) {
                  this._registrations.delete(registration.id);
                  this.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.RegistrationDeleted, registration);
              } else {
                  this.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.RegistrationUpdated, registration);
              }
          }
      },
  
      /**
       * @param {!ServiceWorkerAgent.ServiceWorkerErrorMessage} payload
       */
       _workerErrorReported: function(payload)
      {
          var registration = this._registrations.get(payload.registrationId);
          if (!registration)
              return;
          registration._addError(payload);
          this.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.RegistrationUpdated, registration);
      },
  
      /**
       * @param {boolean} flag
       */
      _debugOnStartUpdated: function(flag)
      {
          this._debugOnStart = flag;
          this.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.DebugOnStartUpdated, flag);
      },
  
      /**
       * @param {!WebInspector.Event} event
       */
      _mainFrameNavigated: function(event)
      {
          // Attach to the new worker set.
      },
  
      __proto__: WebInspector.SDKObject.prototype
  }
  
  /**
   * @constructor
   * @param {!WebInspector.ServiceWorkerManager} manager
   * @param {string} workerId
   * @param {string} url
   * @param {string} versionId
   */
  WebInspector.ServiceWorker = function(manager, workerId, url, versionId)
  {
      this._manager = manager;
      this._agent = manager.target().serviceWorkerAgent();
      this._workerId = workerId;
      this._connection = new WebInspector.ServiceWorkerConnection(this._agent, workerId);
      this._url = url;
      this._versionId = versionId;
      var parsedURL = url.asParsedURL();
      this._name = parsedURL ? parsedURL.lastPathComponentWithFragment()  : "#" + (++WebInspector.ServiceWorker._lastAnonymousTargetId);
      this._scope = parsedURL.host + parsedURL.folderPathComponents;
      var title = WebInspector.UIString("\u2699 %s", this._name);
  
      this._manager._workers.set(workerId, this);
      WebInspector.targetManager.createTarget(title, WebInspector.Target.Type.ServiceWorker, this._connection, manager.target(), targetCreated.bind(this));
  
      /**
       * @param {?WebInspector.Target} target
       * @this {WebInspector.ServiceWorker}
       */
      function targetCreated(target)
      {
          if (!this._connection || !target) {
              this._manager._workers.delete(workerId);
              return;
          }
          this._target = target;
          this._manager.dispatchEventToListeners(WebInspector.ServiceWorkerManager.Events.WorkersUpdated);
          target.runtimeAgent().run();
      }
  }
  
  WebInspector.ServiceWorker._lastAnonymousTargetId = 0;
  
  WebInspector.ServiceWorker.prototype = {
      /**
       * @return {string}
       */
      name: function()
      {
          return this._name;
      },
  
      /**
       * @return {string}
       */
      url: function()
      {
          return this._url;
      },
  
      /**
       * @return {string}
       */
      versionId: function()
      {
          return this._versionId;
      },
  
      /**
       * @return {string}
       */
      scope: function()
      {
          return this._scope;
      },
  
      stop: function()
      {
          this._agent.stop(this._workerId);
      },
  
      _closeConnection: function()
      {
          if (!this._target)
              return;
          this._connection._close();
          delete this._connection;
      }
  }
  
  /**
   * @constructor
   * @implements {ServiceWorkerAgent.Dispatcher}
   * @param {!WebInspector.ServiceWorkerManager} manager
   */
  WebInspector.ServiceWorkerDispatcher = function(manager)
  {
      this._manager = manager;
  }
  
  WebInspector.ServiceWorkerDispatcher.prototype = {
      /**
       * @override
       * @param {string} workerId
       * @param {string} url
       * @param {string} versionId
       */
      workerCreated: function(workerId, url, versionId)
      {
          this._manager._workerCreated(workerId, url, versionId);
      },
  
      /**
       * @override
       * @param {string} workerId
       */
      workerTerminated: function(workerId)
      {
          this._manager._workerTerminated(workerId);
      },
  
      /**
       * @override
       * @param {string} workerId
       * @param {string} message
       */
      dispatchMessage: function(workerId, message)
      {
          this._manager._dispatchMessage(workerId, message);
      },
  
      /**
       * @override
       * @param {!Array.<!ServiceWorkerAgent.ServiceWorkerRegistration>} registrations
       */
      workerRegistrationUpdated: function(registrations)
      {
          this._manager._workerRegistrationUpdated(registrations);
      },
  
      /**
       * @override
       * @param {!Array.<!ServiceWorkerAgent.ServiceWorkerVersion>} versions
       */
      workerVersionUpdated: function(versions)
      {
          this._manager._workerVersionUpdated(versions);
      },
  
      /**
       * @override
       * @param {!ServiceWorkerAgent.ServiceWorkerErrorMessage} errorMessage
       */
      workerErrorReported: function(errorMessage)
      {
          this._manager._workerErrorReported(errorMessage);
      },
  
      /**
       * @override
       * @param {boolean} flag
       */
      debugOnStartUpdated: function(flag)
      {
          this._manager._debugOnStartUpdated(flag);
      }
  }
  
  /**
   * @constructor
   * @extends {InspectorBackendClass.Connection}
   * @param {!Protocol.ServiceWorkerAgent} agent
   * @param {string} workerId
   */
  WebInspector.ServiceWorkerConnection = function(agent, workerId)
  {
      InspectorBackendClass.Connection.call(this);
      //FIXME: remove resourceTreeModel and others from worker targets
      this.suppressErrorsForDomains(["Worker", "Page", "CSS", "DOM", "DOMStorage", "Database", "Network", "IndexedDB"]);
      this._agent = agent;
      this._workerId = workerId;
  }
  
  WebInspector.ServiceWorkerConnection.prototype = {
      /**
       * @override
       * @param {!Object} messageObject
       */
      sendMessage: function(messageObject)
      {
          this._agent.sendMessage(this._workerId, JSON.stringify(messageObject));
      },
  
      _close: function()
      {
          this.connectionClosed("worker_terminated");
      },
  
      __proto__: InspectorBackendClass.Connection.prototype
  }
  
  /**
   * @constructor
   * @param {!ServiceWorkerAgent.TargetInfo} payload
   */
  WebInspector.TargetInfo = function(payload)
  {
      this.id = payload.id;
      this.type = payload.type;
      this.title = payload.title;
      this.url = payload.url;
  }
  
  WebInspector.TargetInfo.prototype = {
      /**
       * @return {boolean}
       */
      isWebContents: function()
      {
          return this.type == "web_contents";
      },
      /**
       * @return {boolean}
       */
      isFrame: function()
      {
          return this.type == "frame";
      },
  }
  
  /**
   * @constructor
   * @param {!ServiceWorkerAgent.ServiceWorkerErrorMessage} payload
   */
  WebInspector.ServiceWorkerErrorMessage = function(payload)
  {
      this.errorMessage = payload.errorMessage;
      this.sourceURL = payload.sourceURL;
      this.lineNumber = payload.lineNumber;
      this.columnNumber = payload.columnNumber;
  }
  
  /**
   * @constructor
   * @param {!WebInspector.ServiceWorkerRegistration} registration
   * @param {!ServiceWorkerAgent.ServiceWorkerVersion} payload
   */
  WebInspector.ServiceWorkerVersion = function(registration, payload)
  {
      this.registration = registration;
      this._update(payload);
      /** @type {!Array<!WebInspector.ServiceWorkerErrorMessage>} */
      this.errorMessages = [];
  }
  
  /**
   * @enum {string}
   */
  WebInspector.ServiceWorkerVersion.Modes = {
      Installing: "installing",
      Waiting: "waiting",
      Active: "active",
      Redundant: "redundant"
  }
  
  WebInspector.ServiceWorkerVersion.prototype = {
      /**
       * @param {!ServiceWorkerAgent.ServiceWorkerVersion} payload
       */
      _update: function(payload)
      {
          this.id = payload.versionId;
          this.scriptURL = payload.scriptURL;
          this.runningStatus = payload.runningStatus;
          this.status = payload.status;
          this.scriptLastModified = payload.scriptLastModified;
          this.scriptResponseTime = payload.scriptResponseTime;
          this.controlledClients = []
          for (var i = 0; i < payload.controlledClients.length; ++i) {
              this.controlledClients.push(payload.controlledClients[i]);
          }
      },
  
      /**
       * @return {boolean}
       */
      isStartable: function()
      {
          return !this.registration.isDeleted && this.isActivated() && this.isStopped();
      },
  
      /**
       * @return {boolean}
       */
      isStoppedAndRedundant: function()
      {
          return this.runningStatus == ServiceWorkerAgent.ServiceWorkerVersionRunningStatus.Stopped && this.status == ServiceWorkerAgent.ServiceWorkerVersionStatus.Redundant;
      },
  
      /**
       * @return {boolean}
       */
      isStopped: function()
      {
          return this.runningStatus == ServiceWorkerAgent.ServiceWorkerVersionRunningStatus.Stopped;
      },
  
      /**
       * @return {boolean}
       */
      isStarting: function()
      {
          return this.runningStatus == ServiceWorkerAgent.ServiceWorkerVersionRunningStatus.Starting;
      },
  
      /**
       * @return {boolean}
       */
      isRunning: function()
      {
          return this.runningStatus == ServiceWorkerAgent.ServiceWorkerVersionRunningStatus.Running;
      },
  
      /**
       * @return {boolean}
       */
      isStopping: function()
      {
          return this.runningStatus == ServiceWorkerAgent.ServiceWorkerVersionRunningStatus.Stopping;
      },
  
      /**
       * @return {boolean}
       */
      isNew: function()
      {
          return this.status == ServiceWorkerAgent.ServiceWorkerVersionStatus.New;
      },
  
      /**
       * @return {boolean}
       */
      isInstalling: function()
      {
          return this.status == ServiceWorkerAgent.ServiceWorkerVersionStatus.Installing;
      },
  
      /**
       * @return {boolean}
       */
      isInstalled: function()
      {
          return this.status == ServiceWorkerAgent.ServiceWorkerVersionStatus.Installed;
      },
  
      /**
       * @return {boolean}
       */
      isActivating: function()
      {
          return this.status == ServiceWorkerAgent.ServiceWorkerVersionStatus.Activating;
      },
  
      /**
       * @return {boolean}
       */
      isActivated: function()
      {
          return this.status == ServiceWorkerAgent.ServiceWorkerVersionStatus.Activated;
      },
  
      /**
       * @return {boolean}
       */
      isRedundant: function()
      {
          return this.status == ServiceWorkerAgent.ServiceWorkerVersionStatus.Redundant;
      },
  
      /**
       * @return {string}
       */
      mode: function()
      {
          if (this.isNew() || this.isInstalling())
              return WebInspector.ServiceWorkerVersion.Modes.Installing;
          else if (this.isInstalled())
              return WebInspector.ServiceWorkerVersion.Modes.Waiting;
          else if (this.isActivating() || this.isActivated())
              return WebInspector.ServiceWorkerVersion.Modes.Active;
          return WebInspector.ServiceWorkerVersion.Modes.Redundant;
      },
  
      /**
       * @param {!ServiceWorkerAgent.ServiceWorkerErrorMessage} payload
       */
      _addError: function(payload)
      {
          this.errorMessages.push(new WebInspector.ServiceWorkerErrorMessage(payload));
      }
  }
  
  /**
  * @constructor
  * @param {!ServiceWorkerAgent.ServiceWorkerRegistration} payload
  */
  WebInspector.ServiceWorkerRegistration = function(payload)
  {
     this._update(payload);
     /** @type {!Map.<string, !WebInspector.ServiceWorkerVersion>} */
     this.versions = new Map();
     this._deleting = false;
  }
  
  WebInspector.ServiceWorkerRegistration.prototype = {
      /**
       * @param {!ServiceWorkerAgent.ServiceWorkerRegistration} payload
       */
      _update: function(payload)
      {
          this.id = payload.registrationId;
          this.scopeURL = payload.scopeURL;
          this.isDeleted = payload.isDeleted;
          this.forceUpdateOnPageLoad = payload.forceUpdateOnPageLoad;
      },
  
      /**
       * @param {!ServiceWorkerAgent.ServiceWorkerVersion} payload
       * @return {!WebInspector.ServiceWorkerVersion}
       */
      _updateVersion: function(payload)
      {
          var version = this.versions.get(payload.versionId);
          if (!version) {
              version = new WebInspector.ServiceWorkerVersion(this, payload);
              this.versions.set(payload.versionId, version);
              return version;
          }
          version._update(payload);
          return version;
      },
  
      /**
       * @param {!ServiceWorkerAgent.ServiceWorkerErrorMessage} payload
       */
      _addError: function(payload)
      {
          var version = this.versions.get(payload.versionId);
          if (version)
              version._addError(payload);
      },
  
      /**
       * @return {boolean}
       */
      _isRedundant: function()
      {
          for (var version of this.versions.values()) {
              if (!version.isStoppedAndRedundant())
                  return false;
          }
          return true;
      },
  
      /**
       * @return {boolean}
       */
      _hasErrorLog: function()
      {
          for (var version of this.versions.values()) {
              if (version.errorMessages.length)
                  return true;
          }
          return false;
      },
  
      /**
       * @return {boolean}
       */
      _shouldBeRemoved: function()
      {
          return this._isRedundant() && (!this._hasErrorLog() || this._deleting);
      }
  }
  