chromium/chrome/test/data/extensions/platform_apps/web_view/isolation_indexeddb/main.js

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var LOG = function(msg) {
  window.console.log(msg);
};

// Wrapper class for a <webview> guest.
function Guest(id, webview) {
  this.id_ = id;
  this.webview_ = webview;
};

// Runs a single verification |step| for this test.
//
// A step is asynchronous and involves sending postMessage to a <webview> and
// receiving a reply postMessage back from the <webview>.
Guest.prototype.sendStepRequestAndWait = function(
    step, successCallback, failureCallback) {
  this.expectedMessage_ = step.expect;
  this.successCallback_ = successCallback;
  this.failureCallback_ = failureCallback;
  this.expectingResponse_ = true;
  step.id = this.id_;
  this.webview_.contentWindow.postMessage(JSON.stringify(step), '*');
};

// Runs verification steps for this test listed in |stepList|.
Guest.prototype.runIDBSteps = function(
    stepList, successCallback, failureCallback) {
  if (!stepList.length) {
    LOG('error, runIDBSteps() require at least one step to run');
    failureCallback();
    return;
  }

  var currentStep = 0;
  // Proceeds to next step upon success.
  var stepSuccessCallback = function() {
    ++currentStep;
    if (currentStep == stepList.length) {
      // All steps succeeded.
      successCallback();
      return;
    }
    this.sendStepRequestAndWait(stepList[currentStep], stepSuccessCallback,
                                failureCallback);
  }.bind(this);
  this.sendStepRequestAndWait(stepList[currentStep], stepSuccessCallback,
                              failureCallback);
};

// Post message handler for this |Guest|.
Guest.prototype.onResponse = function(responseStr) {
  if (!this.expectingResponse_) {
    return;
  }
  if (this.expectedMessage_ !== responseStr) {
    LOG('Expected response from guest[' + this.id_ + ']: ' +
        this.expectedMessage_ + ' but received: ' +
        responseStr);
    this.failureCallback_();
  } else {
    this.successCallback_();
  }
};

// Tester class to run test steps.
function Tester(port) {
  this.port_ = port;
  window.onmessage = this.onMessage_.bind(this);
  this.failed_ = false;
};

Tester.prototype.getURL = function(filename) {
  return 'http://localhost:' + this.port_ +
      '/extensions/platform_apps/web_view/isolation_indexeddb/' + filename;
};

Tester.prototype.loadGuest = function(guestInfo, callback) {
  var id = guestInfo.id;
  var partition = guestInfo.partition;
  var html = guestInfo.html;
  var js = guestInfo.js;

  var webview = document.createElement('webview');
  if (partition) {
    webview.setAttribute('partition', partition);
  }
  webview.src = this.getURL(html);

  var loadFailed = false;
  webview.onloadstop = function(e) {
    LOG('webview.onloadstop: ' + id);
    if (loadFailed) {
      return;
    }

    var guest = new Guest(id, webview);
    webview.executeScript({file: js} , function(results) {
      if (!results || !results.length) {
        loadFailed = true;
        callback(null);
      }
      callback(id, new Guest(id, webview));
    }.bind(this));
  }.bind(this);

  webview.onloadabort = function(e) {
    loadFailed = true;
    callback(undefined);
  };

  webview.onconsolemessage = function(e) {
    LOG('G: ' + e.message);
  };
  document.body.appendChild(webview);
};

Tester.prototype.loadGuests = function(guestInfoList, doneCallback) {
  var failed = false;
  var responses = [];
  var numResponses = 0;
  var completedCallack = function(id, guest) {
    if (failed) {  // We've already failed.
      return;
    }
    if (!guest) {
      // guest didn't load.
      failed = true;
      doneCallback(undefined);
      return;
    }
    responses[id] = guest;
    ++numResponses;
    if (numResponses == guestInfoList.length) {
      // We are done.
      doneCallback(responses);
    }
  };
  for (var i = 0; i < guestInfoList.length; ++i) {
    this.loadGuest(guestInfoList[i], completedCallack);
  }
};

Tester.prototype.runTest = function() {
  var guestInfoList = [
    {id: 1, html: 'storage1.html', js: 'storage.js', partition: 'partition1'},
    {id: 2, html: 'storage2.html', js: 'storage.js', partition: 'partition1'},
    {id: 3, html: 'storage3.html', js: 'storage.js'}
  ];
  this.loadGuests(guestInfoList, function doneCallback(guests) {
    if (!guests) {
      LOG('One or all guests failed to load');
      this.testFail();
      return;
    }

    LOG('guests load complete');
    this.guests_ = guests;
    // Loaded all the guests.
    this.runStep1();
  }.bind(this));
};

// Initializes the storage for the first <webview>.
Tester.prototype.runStep1 = function() {
  var guest = this.guests_[1];
  guest.runIDBSteps([
      {name: 'init', expect: 'idb created'},
      {name: 'add', params: [7, 'page1'], expect: 'addItemIDB complete'},
      {name: 'read', params: [7], expect: 'readItemIDB: page1'},
  ], this.runStep2.bind(this), this.testFail.bind(this));
};

// Initializes the storage for the second <webview>, which share a storage
// partition with the first <webview>.
Tester.prototype.runStep2 = function() {
  var guest = this.guests_[2];
  guest.runIDBSteps([
      {name: 'init', expect: 'idb open'},
      {name: 'add', params: [7, 'page2'], expect: 'addItemIDB complete'},
      {name: 'read', params: [7], expect: 'readItemIDB: page2'},
  ], this.runStep3.bind(this), this.testFail.bind(this));
};

// Reads through the first <webview> to ensure we have the second value.
Tester.prototype.runStep3 = function() {
  var guest = this.guests_[1];
  guest.runIDBSteps([
      {name: 'read', params: [7], expect: 'readItemIDB: page2'},
  ], this.runStep4.bind(this), this.testFail.bind(this));
};

// Confirms that the first two <webview>s do not affect the database
// of the main browser (embedder).
Tester.prototype.runStep4 = function() {
  var request = indexedDB.open('isolation');
  request.onsuccess = function(e) {
    var version = e.target.result.version;
    // Expect version = 1.
    if (version == 1) {
      this.runStep5();  // Continue to next step.
    } else {
      this.testFail();
    }
  }.bind(this);
  request.onerror = this.testFail.bind(this);
  request.onblocked = this.testFail.bind(this);
};

// Confirms that a third <webview>'s storage does not get affect by the
// other two <webview>s.
Tester.prototype.runStep5 = function() {
  var guest = this.guests_[3];
  guest.runIDBSteps([
      {name: 'open', params: ['isolation'], expect: 'db not found'},
  ], this.testPass(this), this.testFail.bind(this));
};

// Central postMessage handler.
Tester.prototype.onMessage_ = function(e) {
  var data = JSON.parse(e.data);
  var id = data[0];
  if (typeof id !== 'number' || id < 0 || id >= this.guests_.length) {
    LOG('unexpeced guest id: ' + id);
    this.testFail();
    return;
  }

  // Re-route message to the appropriate guest.
  this.guests_[id].onResponse(data[1]);
};

Tester.prototype.testFail = function() {
  this.failed_ = true;
  chrome.test.fail();
};

Tester.prototype.testPass = function() {
  if (this.failed_) {  // We've failed already.
    return;
  }
  chrome.test.succeed();
};

chrome.test.getConfig(function(config) {
  chrome.test.runTests([
      function indexedDBIsolation() {
        var tester = new Tester(config.testServer.port);
        tester.runTest();
      }]);
});