Remove experimental.record api completely

BUG=259492
TBR=isherman@chromium.org

Review URL: https://chromiumcodereview.appspot.com/21854002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@215835 0039d316-1c4b-4281-b951-d872f2087c98
parent 053a8b9f
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/record/record_api.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/experimental_record.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
namespace extensions {
namespace record = api::experimental_record;
ProcessStrategy::~ProcessStrategy() {}
void ProductionProcessStrategy::RunProcess(const CommandLine& line,
std::vector<std::string>* errors) {
base::LaunchOptions options;
base::ProcessHandle handle;
base::LaunchProcess(line, options, &handle);
int exit_code = 0;
if (!base::WaitForExitCode(handle, &exit_code) || exit_code != 0)
errors->push_back("Test browser exited abnormally");
}
RunPageCyclerFunction::RunPageCyclerFunction(ProcessStrategy* strategy)
: repeat_count_(1), base_command_line_(*CommandLine::ForCurrentProcess()),
process_strategy_(strategy) {}
RunPageCyclerFunction::~RunPageCyclerFunction() {}
bool RunPageCyclerFunction::RunImpl() {
if (!ParseJSParameters())
return false;
// If we've had any errors reportable to the JS caller so far (in
// parameter parsing) then return a list of such errors, else perform
// RunTestBrowser on the BlockingPool.
if (!errors_.empty()) {
results_ = record::CaptureURLs::Results::Create(errors_);
SendResponse(true);
} else {
content::BrowserThread::PostBlockingPoolTask(FROM_HERE,
base::Bind(&RunPageCyclerFunction::RunTestBrowser, this));
process_strategy_->PumpBlockingPool(); // Test purposes only.
}
return true;
}
CommandLine RunPageCyclerFunction::RemoveSwitches(const CommandLine& original,
const std::vector<std::string>& to_remove) {
std::vector<const char*> to_keep;
const CommandLine::SwitchMap& current_switches = original.GetSwitches();
CommandLine filtered(original.GetProgram());
// Retain in |to_keep| all current swtiches *not* in |to_remove|.
for (CommandLine::SwitchMap::const_iterator itr = current_switches.begin();
itr != current_switches.end(); ++itr) {
if (std::find(to_remove.begin(), to_remove.end(), (*itr).first) ==
to_remove.end()) {
to_keep.push_back((*itr).first.c_str());
}
}
// Rely on std::vector keeping its contents in contiguous order.
// (This is documented STL spec.)
filtered.CopySwitchesFrom(original, &to_keep.front(), to_keep.size());
return filtered;
}
// Runs on BlockingPool thread. Invoked from UI thread and passes back to
// UI thread for |Final()| callback to JS side.
void RunPageCyclerFunction::RunTestBrowser() {
// Remove any current switch settings that would interfere with test browser
// commandline setup.
std::vector<std::string> remove_switches;
remove_switches.push_back(switches::kUserDataDir);
remove_switches.push_back(switches::kVisitURLs);
remove_switches.push_back(switches::kPlaybackMode);
remove_switches.push_back(switches::kRecordStats);
remove_switches.push_back(switches::kLoadExtension);
CommandLine line = RemoveSwitches(base_command_line_, remove_switches);
// Add the user-data-dir switch, since this is common to both call types.
line.AppendSwitchPath(switches::kUserDataDir, user_data_dir_);
// Test browsers must run as if they are not in first-run mode
line.AppendSwitch(switches::kNoFirstRun);
// Create and fill a temp file to communicate the URL list to the test
// browser.
base::FilePath url_path;
file_util::CreateTemporaryFile(&url_path);
file_util::WriteFile(url_path, url_contents_.c_str(), url_contents_.size());
line.AppendSwitchPath(switches::kVisitURLs, url_path);
// Set up Capture- or Replay-specific commandline switches.
AddSwitches(&line);
base::FilePath error_file_path = url_path.DirName().
Append(url_path.BaseName().value() +
base::FilePath::StringType(kURLErrorsSuffix));
LOG(ERROR) << "Test browser commandline: " << line.GetCommandLineString() <<
" will be repeated " << repeat_count_ << " times....";
// Run the test browser (or a mockup, depending on |process_strategy_|.
while (repeat_count_-- && errors_.empty() &&
!base::PathExists(error_file_path))
process_strategy_->RunProcess(line, &errors_);
// Read URL errors file if there is one, and save errors in |errors_|.
// Odd extension handling needed because temp files have lots of "."s in
// their names, and we need to cleanly add kURLErrorsSuffix as a final
// extension.
if (errors_.empty() && base::PathExists(error_file_path)) {
std::string error_content;
file_util::ReadFileToString(error_file_path, &error_content);
base::SplitString(error_content, '\n', &errors_);
}
// Do any special post-test-browser file reading (e.g. stats report))
// while we're on the BlockingPool thread.
ReadReplyFiles();
// Back to UI thread to finish up the JS call.
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&RunPageCyclerFunction::Finish, this));
}
const ProcessStrategy &RunPageCyclerFunction::GetProcessStrategy() {
return *process_strategy_;
}
// RecordCaptureURLsFunction ------------------------------------------------
RecordCaptureURLsFunction::RecordCaptureURLsFunction()
: RunPageCyclerFunction(new ProductionProcessStrategy()) {}
RecordCaptureURLsFunction::RecordCaptureURLsFunction(ProcessStrategy* strategy)
: RunPageCyclerFunction(strategy) {}
// Fetch data for possible optional switch for an extension to load.
bool RecordCaptureURLsFunction::ParseJSParameters() {
scoped_ptr<record::CaptureURLs::Params> params(
record::CaptureURLs::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
url_contents_ = JoinString(params->urls, '\n');
// TODO(cstaley): Can't just use captureName -- gotta stick it in a temp dir.
// TODO(cstaley): Ensure that capture name is suitable as directory name.
user_data_dir_ = base::FilePath::FromUTF8Unsafe(params->capture_name);
return true;
}
// RecordCaptureURLsFunction adds "record-mode" to sub-browser call, and returns
// just the (possibly empty) error list.
void RecordCaptureURLsFunction::AddSwitches(CommandLine* line) {
if (!line->HasSwitch(switches::kRecordMode))
line->AppendSwitch(switches::kRecordMode);
}
void RecordCaptureURLsFunction::Finish() {
results_ = record::CaptureURLs::Results::Create(errors_);
SendResponse(true);
}
// RecordReplayURLsFunction ------------------------------------------------
RecordReplayURLsFunction::RecordReplayURLsFunction()
: RunPageCyclerFunction(new ProductionProcessStrategy()),
run_time_ms_(0.0) {
}
RecordReplayURLsFunction::RecordReplayURLsFunction(ProcessStrategy* strategy)
: RunPageCyclerFunction(strategy), run_time_ms_(0.0) {
}
RecordReplayURLsFunction::~RecordReplayURLsFunction() {}
// Fetch data for possible optional switches for a repeat count and an
// extension to load.
bool RecordReplayURLsFunction::ParseJSParameters() {
scoped_ptr<record::ReplayURLs::Params> params(
record::ReplayURLs::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
// TODO(cstaley): Must build full temp dir from capture_name
user_data_dir_ = base::FilePath::FromUTF8Unsafe(params->capture_name);
// TODO(cstaley): Get this from user data dir ultimately
url_contents_ = "http://www.google.com\nhttp://www.amazon.com";
repeat_count_ = params->repeat_count;
if (params->details.get()) {
if (params->details->extension_path.get())
extension_path_ =
base::FilePath::FromUTF8Unsafe(*params->details->extension_path);
}
return true;
}
// Add special switches, if indicated, for repeat count and extension to load,
// plus temp file into which to place stats. (Can't do this in
// ParseJSParameters because file creation can't go on the UI thread.)
// Plus, initialize time to create run time statistic.
void RecordReplayURLsFunction::AddSwitches(CommandLine* line) {
file_util::CreateTemporaryFile(&stats_file_path_);
if (!extension_path_.empty())
line->AppendSwitchPath(switches::kLoadExtension, extension_path_);
line->AppendSwitch(switches::kPlaybackMode);
line->AppendSwitchPath(switches::kRecordStats, stats_file_path_);
timer_ = base::Time::NowFromSystemTime();
}
// Read stats file, and get run time.
void RecordReplayURLsFunction::ReadReplyFiles() {
file_util::ReadFileToString(stats_file_path_, &stats_);
run_time_ms_ = (base::Time::NowFromSystemTime() - timer_).InMillisecondsF();
}
void RecordReplayURLsFunction::Finish() {
record::ReplayURLsResult result;
result.run_time = run_time_ms_;
result.stats = stats_;
result.errors = errors_;
results_ = record::ReplayURLs::Results::Create(result);
SendResponse(true);
}
} // namespace extensions
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_API_RECORD_RECORD_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_RECORD_RECORD_API_H_
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/time/time.h"
#include "chrome/browser/extensions/extension_function.h"
namespace {
const base::FilePath::CharType kURLErrorsSuffix[] =
FILE_PATH_LITERAL(".errors");
const char kErrorsKey[] = "errors";
const char kStatsKey[] = "stats";
};
namespace extensions {
// ProcessStrategy abstracts the API's starting and waiting on a test
// browser instance. This lets us browser-test the API without actually
// firing up a sub browser instance.
class ProcessStrategy {
public:
// Needed to void build warnings
virtual ~ProcessStrategy();
// Used only in test version to pump the blocking pool queue,
// which doesn't otherwise happen during test.
virtual void PumpBlockingPool() {}
// Start up process with given commandline. Real version does just
// that; test version mocks it up, generating errors or good results,
// as configured. If there are any problems running the process itself,
// as opposed to errors in the cycler URL list, etc, report these via
// |errors|
virtual void RunProcess(const CommandLine& line,
std::vector<std::string> *errors) = 0;
};
// Production (default) version of ProcessStrategy. See ProcessStrategy
// comments for more info. This subclass actually starts a sub browser
// instance.
class ProductionProcessStrategy : public ProcessStrategy {
public:
virtual void RunProcess(const CommandLine& line,
std::vector<std::string> *errors) OVERRIDE;
};
// Both page cycler calls (capture and replay) have a great deal in common,
// including the need to build and write a url list file, set the user
// data dir, start a sub-instance of Chrome, and parse a resultant error
// file. This base class encapslates those common elements.
class RunPageCyclerFunction : public AsyncExtensionFunction {
public:
explicit RunPageCyclerFunction(ProcessStrategy* strategy);
// Make a CommandLine copy of |original|, removing all switches in
// |to_remove|.
static CommandLine RemoveSwitches(const CommandLine& original,
const std::vector<std::string>& to_remove);
// Return ProcessStrategy, to allow for test versions.
virtual const ProcessStrategy &GetProcessStrategy();
protected:
virtual ~RunPageCyclerFunction();
// Gather common page cycler parameters and store them, then do blocking
// thread invocation of RunTestBrowser.
virtual bool RunImpl() OVERRIDE;
// Parse the JS parameters, and store them as member data.
virtual bool ParseJSParameters() = 0;
// Do a vanilla test browser run, bracketing it immediately before and
// after with a call of AddSwitches to add special commandline options
// for Capture or Replay, and ReadReplyFiles to do any special post-run
// data collection. Gather any error results into |errors_| and then do a
// BrowserThread call of Finish to return JS values.
virtual void RunTestBrowser();
virtual void AddSwitches(CommandLine* command_line) {}
// The test browser communicates URL errors, performance statistics, and
// possibly other data by posting them to text files. ReadReplyFiles
// collects these data for return to the JS side caller.
virtual void ReadReplyFiles() {}
// Return the values gathered in RunTestBrowser. No common code here; all
// logic is in subclasses.
virtual void Finish() {}
base::FilePath user_data_dir_;
std::string url_contents_;
int repeat_count_;
std::vector<std::string> errors_;
// Base CommandLine on which to build the test browser CommandLine
CommandLine base_command_line_;
// ProcessStrategy to use for this run.
scoped_ptr<ProcessStrategy> process_strategy_;
};
class RecordCaptureURLsFunction : public RunPageCyclerFunction {
public:
DECLARE_EXTENSION_FUNCTION("experimental.record.captureURLs",
EXPERIMENTAL_RECORD_CAPTUREURLS)
RecordCaptureURLsFunction();
explicit RecordCaptureURLsFunction(ProcessStrategy* strategy);
private:
virtual ~RecordCaptureURLsFunction() {}
// Read the ReplayDetails parameter if it exists.
virtual bool ParseJSParameters() OVERRIDE;
// Add record-mode.
virtual void AddSwitches(CommandLine* command_line) OVERRIDE;
// Return error list.
virtual void Finish() OVERRIDE;
};
class RecordReplayURLsFunction : public RunPageCyclerFunction {
public:
DECLARE_EXTENSION_FUNCTION("experimental.record.replayURLs",
EXPERIMENTAL_RECORD_REPLAYURLS)
RecordReplayURLsFunction();
explicit RecordReplayURLsFunction(ProcessStrategy* strategy);
private:
virtual ~RecordReplayURLsFunction();
// Read the ReplayDetails parameter if it exists.
virtual bool ParseJSParameters() OVERRIDE;
// Add visit-urls-count and load-extension.
virtual void AddSwitches(CommandLine* command_line) OVERRIDE;
// Read stats file.
virtual void ReadReplyFiles() OVERRIDE;
// Return error list, statistical results, and runtime.
virtual void Finish() OVERRIDE;
// These data are additional information added to the sub-browser
// commandline or used to repeatedly run the sub-browser.
base::FilePath extension_path_;
base::FilePath stats_file_path_;
// This time datum marks the start and end of the sub-browser run.
base::Time timer_;
// These two data are additional information returned to caller.
double run_time_ms_;
std::string stats_;
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_RECORD_RECORD_API_H_
...@@ -240,7 +240,7 @@ enum HistogramValue { ...@@ -240,7 +240,7 @@ enum HistogramValue {
BOOKMARKS_GETTREE, BOOKMARKS_GETTREE,
FILEBROWSERPRIVATE_SELECTFILES, FILEBROWSERPRIVATE_SELECTFILES,
RUNTIME_GETBACKGROUNDPAGE, RUNTIME_GETBACKGROUNDPAGE,
EXPERIMENTAL_RECORD_REPLAYURLS, DELETED_EXPERIMENTAL_RECORD_REPLAYURLS,
WEBSTOREPRIVATE_COMPLETEINSTALL, WEBSTOREPRIVATE_COMPLETEINSTALL,
DELETED_EXPERIMENTAL_SPEECHINPUT_START, DELETED_EXPERIMENTAL_SPEECHINPUT_START,
COOKIES_GETALL, COOKIES_GETALL,
...@@ -410,7 +410,7 @@ enum HistogramValue { ...@@ -410,7 +410,7 @@ enum HistogramValue {
CONTEXTMENUS_CREATE, CONTEXTMENUS_CREATE,
MEDIAPLAYERPRIVATE_GETPLAYLIST, MEDIAPLAYERPRIVATE_GETPLAYLIST,
DOWNLOADS_ERASE, DOWNLOADS_ERASE,
EXPERIMENTAL_RECORD_CAPTUREURLS, DELETED_EXPERIMENTAL_RECORD_CAPTUREURLS,
TTS_ISSPEAKING, TTS_ISSPEAKING,
BOOKMARKS_REMOVETREE, BOOKMARKS_REMOVETREE,
FILEBROWSERPRIVATE_SEARCHDRIVE, FILEBROWSERPRIVATE_SEARCHDRIVE,
......
...@@ -376,8 +376,6 @@ ...@@ -376,8 +376,6 @@
'browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h', 'browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h',
'browser/extensions/api/push_messaging/push_messaging_invalidation_handler_delegate.h', 'browser/extensions/api/push_messaging/push_messaging_invalidation_handler_delegate.h',
'browser/extensions/api/push_messaging/push_messaging_invalidation_mapper.h', 'browser/extensions/api/push_messaging/push_messaging_invalidation_mapper.h',
'browser/extensions/api/record/record_api.cc',
'browser/extensions/api/record/record_api.h',
'browser/extensions/api/recovery_private/recovery_operation.cc', 'browser/extensions/api/recovery_private/recovery_operation.cc',
'browser/extensions/api/recovery_private/recovery_operation.h', 'browser/extensions/api/recovery_private/recovery_operation.h',
'browser/extensions/api/recovery_private/recovery_operation_manager.cc', 'browser/extensions/api/recovery_private/recovery_operation_manager.cc',
......
...@@ -1324,7 +1324,6 @@ ...@@ -1324,7 +1324,6 @@
'browser/extensions/api/push_messaging/push_messaging_apitest.cc', 'browser/extensions/api/push_messaging/push_messaging_apitest.cc',
'browser/extensions/api/push_messaging/push_messaging_canary_test.cc', 'browser/extensions/api/push_messaging/push_messaging_canary_test.cc',
'browser/extensions/api/push_messaging/sync_setup_helper.cc', 'browser/extensions/api/push_messaging/sync_setup_helper.cc',
'browser/extensions/api/record/record_api_test.cc',
'browser/extensions/api/rtc_private/rtc_private_apitest.cc', 'browser/extensions/api/rtc_private/rtc_private_apitest.cc',
'browser/extensions/api/runtime/runtime_apitest.cc', 'browser/extensions/api/runtime/runtime_apitest.cc',
'browser/extensions/api/serial/serial_apitest.cc', 'browser/extensions/api/serial/serial_apitest.cc',
......
...@@ -213,10 +213,6 @@ ...@@ -213,10 +213,6 @@
"dependencies": ["permission:experimental"], "dependencies": ["permission:experimental"],
"contexts": ["blessed_extension"] "contexts": ["blessed_extension"]
}, },
"experimental.record": {
"dependencies": ["permission:experimental"],
"contexts": ["blessed_extension"]
},
"experimental.rlz": { "experimental.rlz": {
"dependencies": ["permission:experimental"], "dependencies": ["permission:experimental"],
"contexts": ["blessed_extension"] "contexts": ["blessed_extension"]
......
...@@ -53,7 +53,6 @@ ...@@ -53,7 +53,6 @@
'experimental_identity.idl', 'experimental_identity.idl',
'experimental_idltest.idl', 'experimental_idltest.idl',
'location.idl', 'location.idl',
'experimental_record.json',
'system_memory.idl', 'system_memory.idl',
'extension.json', 'extension.json',
'feedback_private.idl', 'feedback_private.idl',
......
[
{
"namespace": "experimental.record",
"description": "The <code>chrome.experimental.record</code> API.",
"types": [
{
"id": "SessionDetails",
"type": "object",
"description": "",
"properties": {
"extensionPath": {
"type": "string",
"optional": true,
"description":
"Absolute path to an unpacked extension to run in the subbrowser session."
}
}
},
{
"id": "ReplayURLsResult",
"type": "object",
"description": "Return value for Replay callback",
"properties": {
"runTime": {
"type": "number",
"description": "Time in milliseconds to complete all runs."
},
"stats": {
"type": "string",
"description": "Full multiline dump of output stats, showing one statistic per line, comprising an abbreviated statistic name and its value (e.g. vmsize_f_b= 696164352 bytes for final vm size). This is ugly, and will be changed shortly."
},
"errors": {
"type": "array",
"items": {"type": "string"},
"description": "List of errors during replay. Presently, this should only be abnormal browser termination for unexpected reasons."
}
}
}
],
"functions": [
{
"name": "captureURLs",
"description": "",
"type": "function",
"parameters": [
{
"type": "string",
"description": "Unique name of the capture.",
"name": "captureName"
},
{
"type": "array",
"items": {"type": "string"},
"description": "URL list to visit during capture.",
"name": "urls"
},
{
"name": "callback",
"type": "function",
"description": "Called when capture has completed.",
"optional": true,
"parameters": [
{
"type": "array",
"items": {"type": "string"},
"name": "errors",
"description": "List of any URLs that failed to load, one error per textline, along with failure reason (e.g. unknown domain). Also may include general abnormal-exit message if the subbrowser run failed for other reasons."
}
]
}
]
},
{
"name": "replayURLs",
"description": "",
"type": "function",
"parameters": [
{
"type": "string",
"name": "captureName",
"description": "Unique name of capture. Use to determine cache."
},
{
"type": "integer",
"name": "repeatCount",
"minimum": 0,
"maximum": 100
},
{
"$ref": "SessionDetails",
"name": "details",
"optional": true
},
{
"name": "callback",
"type": "function",
"optional": true,
"description": "Called when playback has completed.",
"parameters": [
{
"$ref": "ReplayURLsResult",
"name": "result"
}
]
}
]
}
]
}
]
{
"name": "Page Cycler",
"version": "0.0",
"description": "Page Cycler UI",
"app": {
"launch": {
"local_path": "page_cycler.html"
}
},
"icons": {
"128": "page_cycler_icon.png",
"16": "page_cycler_icon_16.png"
},
"permissions": [
"experimental"
]
}
/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
body {
font-family:sans-serif;
font-size:85%;
}
.control-label {
margin-right:1em;
font-style:bold;
}
.url-list {
width:100%;
height:20em;
border: 1px solid;
}
.entry-field {
margin:10px;
}
.error-list-show {
color:red;
}
.error-list-hide {
display:none;
}
button,input {
}
.command-button {
position:relative;
top:2em;
text-align:center;
}
h1 {
text-align:center;
}
.dir-choice {
display:inline-block;
border: 1px solid;
width:40em;
}
.tab-menu {
position:absolute;
left:0;
width:15em;
}
.tab-label {
margin:1px;
padding:1px;
}
.enabled-tab-label {
border: 1px solid;
border-color:#888;
border-radius:2px;
background:#EFF;
}
.disabled-tab-label {
background-color:#FFF;
}
body, html, #page {
height:100%;
}
div#tab-navigator {
position:relative;
float:left;
width:19%;
}
div#tab-wrapper {
position:relative;
float:right;
width:79%;
}
.splitter {
position:relative;
float:left;
width:5px;
height:100%;
background-color:#CCC;
}
.tab {
position:absolute;
top:0px;
left:0px;
}
<!doctype html>
<!-- Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html>
<head>
<title>Page Cycler</title>
<link rel="stylesheet" type="text/css" href="page_cycler.css"/>
</head>
<body>
<div id="page">
<div id="tab-navigator">
<div class="tab-label" id="capture-tab-label"><a>Prepare Test</a></div>
<div class="tab-label" id="replay-tab-label"><a>Run Test</a></div>
</div>
<div class="splitter"></div>
<div id="tab-wrapper">
<div id="capture-tab" class="tab">
<h1>Set Up Page Cycling Test</h1>
<p>
<b>Set up the list of URLs, and a cache directory. "Prepare Test" will
do a preliminary visit of all the URLs, caching them in the indicated
directory.</b>
</p>
<div id="capture-errors-display" class="error-list-hide">
<h2>Errors Occured during Capture:</h2>
<p id="capture-errors"></p>
</div>
<div>
<p class="control-label">List of URLs to cycle</p>
<textarea id="capture-urls" class="url-list">&lt;Add URLS&gt;
</textarea>
</div>
<div class="entry-field">
<span class="control-label">Cache directory:</span><br/>
<input id="capture-cache-dir" type="text" class="dir-choice"/>
</div>
<div class="command-button">
<button id="capture-test">
Prepare Test
</button>
</div>
</div>
<div id="replay-tab" class="tab">
<h1>Replay Prepared Test</h1>
<div id="replay-errors-display" class="error-list-hide">
<h2>Errors Occured during Replay:</h2>
<p id="replay-errors"></p>
</div>
<div>
<p class="control-label">List of URLs to replay</p>
<p id="replay-urls" class="url-list"></p>
</div>
<div class="entry-field">
<span class="control-label">Cache directory: </span>
<span id="replay-cache-dir"></span>
</div>
<div class="entry-field">
<span class="control-label">Repeat count: </span>
<input id="repeat-count" type="text" value="1"/>
</div>
<div class="entry-field">
<span class="control-label">Extension directory: </span>
<input id="extension-dir" type="text" class="dir-choice"/>
</div>
<div>
<p class="control-label">Result of Replay</p>
<textarea id="replay-result" class="url-list">
</textarea>
</div>
<div class="command-button">
<button id="replay-test" disabled=true>
Replay Test
</button>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="page_cycler.js"></script>
</body>
</html>
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function $(criterion) {
return document.querySelector(criterion);
}
var pageCyclerUI = new (function () {
var noTestMessage = "N/A -- do Prepare Test";
this.urlList = [];
this.cacheDir = "";
this.captureTab = $("#capture-tab");
this.captureTabLabel = $("#capture-tab-label");
this.captureButton = $("#capture-test");
this.captureErrorDiv = $("#capture-errors-display");
this.captureErrorList = $("#capture-errors");
this.replayTab = $("#replay-tab");
this.replayTabLabel = $("#replay-tab-label");
this.replayURLs = $("#replay-urls");
this.replayCache = $("#replay-cache-dir");
this.replayButton = $("#replay-test");
this.replayErrorDiv = $("#replay-errors-display");
this.replayErrorList = $("#replay-errors");
this.replayURLs.innerText = this.replayCache.innerText = noTestMessage;
this.enableTab = function(tabLabel, tab) {
var tabList = document.querySelectorAll(".tab");
var tabLabelList = document.querySelectorAll(".tab-label");
for (var i = 0; i < tabList.length; i++)
if (tabList[i] == tab)
tabList[i].style.visibility = "visible";
else
tabList[i].style.visibility = "hidden";
for (var i = 0; i < tabLabelList.length; i++)
if (tabLabelList[i] == tabLabel) {
tabLabelList[i].classList.add("enabled-tab-label");
tabLabelList[i].classList.remove("disabled-tab-label");
} else {
tabLabelList[i].classList.remove("enabled-tab-label");
tabLabelList[i].classList.add("disabled-tab-label");
}
}
this.chooseCapture = function() {
this.enableTab(this.captureTabLabel, this.captureTab);
}
this.chooseReplay = function() {
this.enableTab(this.replayTabLabel, this.replayTab);
}
this.captureTest = function() {
var errorList = $("#capture-errors");
var errors = [];
this.cacheDir = $("#capture-cache-dir").value;
this.urlList = $("#capture-urls").value.split("\n");
if (errors.length > 0) {
this.captureErrorList.innerText = errors.join("\n");
this.captureErrorDiv.className = "error-list-show";
}
else {
this.captureErrorDiv.className = "error-list-hide";
this.captureButton.disabled = true;
chrome.experimental.record.captureURLs(this.urlList, this.cacheDir,
this.onCaptureDone.bind(this));
}
}
this.onCaptureDone = function(errors) {
this.captureButton.disabled = false;
if (errors.length > 0) {
this.captureErrorList.innerText = errors.join("\n");
this.captureErrorDiv.className = "error-list-show";
this.replayButton.disabled = true;
this.replayCache.innerText = this.replayURLs.innerText = noTestMessage;
}
else {
this.captureErrorDiv.className = "error-list-hide";
this.replayButton.disabled = false;
this.replayURLs.innerText = this.urlList.join("\n");
this.replayCache.innerText = this.cacheDir;
}
}
this.replayTest = function() {
var extensionPath = $("#extension-dir").value;
var repeatCount = parseInt($('#repeat-count').value);
var errors = [];
// Check local errors
if (isNaN(repeatCount))
errors.push("Enter a number for repeat count");
else if (repeatCount < 1 || repeatCount > 100)
errors.push("Repeat count must be between 1 and 100");
if (errors.length > 0) {
this.replayErrorList.innerText = errors.join("\n");
this.replayErrorDiv.className = "error-list-show";
} else {
this.replayErrorDiv.className = "error-list-hide";
this.replayButton.disabled = true;
chrome.experimental.record.replayURLs(
this.urlList,
this.cacheDir,
repeatCount,
{"extensionPath": extensionPath},
this.onReplayDone.bind(this));
}
}
this.onReplayDone = function(result) {
var replayResult = $("#replay-result");
this.replayButton.disabled = false;
if (result.errors.length > 0) {
this.replayErrorList.innerText = result.errors.join("<br>");
this.replayErrorDiv.className = "error-list-show";
}
else {
this.replayErrorDiv.className = "error-list-hide";
replayResult.innerText = "Test took " + result.runTime + "mS :\n" +
result.stats;
}
}
this.captureButton.addEventListener("click", this.captureTest.bind(this));
this.replayButton.addEventListener("click", this.replayTest.bind(this));
this.captureTabLabel.addEventListener("click", this.chooseCapture.bind(this));
this.replayTabLabel.addEventListener("click", this.chooseReplay.bind(this));
this.enableTab(this.captureTabLabel, this.captureTab);
})();
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Constructor for the tab UI governing setup and initial running of capture
* baselines. All HTML controls under tag #capture-tab, plus the tab label
* #capture-tab-label are controlled by this class.
* @param {!Object} cyclerUI The master UI class, needed for global state
* such as current capture name.
* @param {!Object} cyclerData The local FileSystem-based database of
* available captures to play back.
* @param {!Object} playbackTab The class governing playback selections.
* We need this in order to update its choices when we get asynchronous
* callbacks for successfully completed capture baselines.
*/
var CaptureTab = function (cyclerUI, cyclerData, playbackTab) {
// Members for all UI elements subject to programmatic adjustment.
this.tabLabel_ = $('#capture-tab-label');
this.captureTab_ = $('#capture-tab');
this.captureName_ = $('#capture-name');
this.captureURLs_ = $('#capture-urls');
this.doCaptureButton_ = $('#do-capture');
// References to other major components of the extension.
this.cyclerUI_ = cyclerUI;
this.cyclerData_ = cyclerData;
this.playbackTab_ = playbackTab;
/*
* Enable the capture tab and its label.
*/
this.enable = function() {
this.captureTab_.hidden = false;
this.tabLabel_.classList.add('selected');
};
/*
* Disable the capture tab and its label.
*/
this.disable = function() {
this.captureTab_.hidden = true;
this.tabLabel_.classList.remove('selected');
};
/**
* Do a capture using current data from the capture tab. Post an error
* dialog if said data is incorrect or incomplete. Otherwise pass
* control to the browser side.
* @private
*/
this.doCapture_ = function() {
var errors = [];
var captureName = this.captureName_.value.trim();
var urlList;
urlList = this.captureURLs_.value.split('\n');
if (captureName.length == 0)
errors.push('Must give a capture name');
if (urlList.length == 0)
errors.push('Must give at least one URL');
if (errors.length > 0) {
this.cyclerUI_.showMessage(errors.join('\n'), 'Ok');
} else {
this.doCaptureButton_.disabled = true;
chrome.experimental.record.captureURLs(captureName, urlList,
this.onCaptureDone.bind(this));
}
}
/**
* Callback for completed (or possibly failed) capture. Post a message
* box, either with errors or "Success!" message.
* @param {!Array.<string>} errors List of errors that occured
* during capture, if any.
*/
this.onCaptureDone = function(errors) {
this.doCaptureButton_.disabled = false;
if (errors.length > 0) {
this.cyclerUI_.showMessage(errors.join('\n'), 'Ok');
} else {
this.cyclerUI_.showMessage('Success!', 'Ok');
this.cyclerUI_.currentCaptureName = this.captureName_.value.trim();
this.cyclerData_.saveCapture(this.cyclerUI_.currentCaptureName);
}
}
// Set up listener for capture button.
this.doCaptureButton_.addEventListener('click', this.doCapture_.bind(this));
};
/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
/* Mimic sans-serif, dark bluish-gray font from Chrome|Settings page.
* This is approximate, as Chrome|Settings can adjust CSS per-platform and
* for instance uses Ubuntu font on Unix. It's at least quite close. */
body {
font-family: Arial, sans-serif;
font-size: 12px;
color: #303942;
}
/* Mimic button from Settings page */
button {
-webkit-padding-end: 10px;
-webkit-padding-start: 10px;
background-color: buttonface;
background-image: -webkit-linear-gradient(#EDEDED, #EDEDED 38%, #DEDEDE);
border: 1px solid rgba(0, 0, 0, 0.25);
border-radius: 2px;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
inset 0 1px 2px rgba(255, 255, 255, 0.75);
color: #444;
font: inherit;
margin: 0 1px 0 0;
min-height: 2em;
min-width: 4em;
text-shadow: #F0F0F0 0px 1px 0px;
}
button:active {
background-image: -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
box-shadow: none;
text-shadow: none;
}
input, select {
width: 15em;
}
/* Top left header and tab headings, respectively */
#header, h1 {
font-size: 18px;
font-weight: normal;
margin-top: 10px;
}
/* h2 for major subheadings has pure black */
h2 {
color: black;
font-size: 14px;
font-weight: normal;
margin-top: 20px;
}
.divider {
background-color: #EEE;
height: 1px;
min-width: 600px;
}
.tab-label {
color: #999;
cursor: pointer;
font-size: 13px;
margin-top: 15px;
}
.selected {
color: #464E5A;
}
.indent {
margin-left: 18px;
}
.sub-label {
min-width: 10em;
}
.column {
-webkit-box-orient: vertical;
display: -webkit-box;
}
.row {
-webkit-box-orient: horizontal;
display: -webkit-box;
}
.gapped {
margin-top: 1em;
}
.url-list {
height: 8em;
width: 20em;
}
/* Cycler header is a little lighter blue-gray */
#header {
color: #5C6166;
margin-bottom: 1.5em;
margin-left: 1em;
}
#header-icon {
float:left;
position:relative;
}
#tab-navigator {
float:left;
position:relative;
width:100px;
}
#tab-wrapper {
margin-left: 100px;
}
#playback-repeats {
width: 3em;
}
#popup {
background-color: rgba(255, 255, 255, .6);
height:100%;
left: 0;
position: absolute;
top: 0;
width:100%;
z-index: 1;
}
#popup-box {
background-color: #fff;
border: 1px solid;
left: 30em;
padding: 1em;
position: absolute;
top: 12em;
z-index: 2;
}
#no-captures {
padding-top: 20px;
}
<!doctype html>
<!-- Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html>
<head>
<title>Page Cycler</title>
<link rel='stylesheet' type='text/css' href='cycler.css'/>
</head>
<body>
<div id='page'>
<div class='row'>
<div id='tab-navigator column'>
<div class='row'>
<img id='header-icon' src='cycler_icon_128.png' height='30'
height='30'/>
<h1 id='header'>Cycler</h1>
</div>
<div id='capture-tab-label' class='tab-label selected'>
<a>Capture</a>
</div>
<div id='playback-tab-label' class='tab-label'>
<a>Playback</a>
</div>
</div>
<div id='tab-wrapper'>
<div id='capture-tab'>
<div class='column'>
<h1>Capture</h1>
<div class='divider'></div>
<h2>Name:</h2>
<input class='indent name-combo' id='capture-name' type='text'/>
<h2>URLs:</h2>
<div class='indent'>
Enter the URLs to capture, one URL per line.<br/>
<textarea id='capture-urls' class='url-list'></textarea>
</div>
<button id='do-capture' class='gapped'>Capture!</button>
</div>
</div>
<div id='playback-tab' hidden>
<div class='column'>
<h1>Playback</h1>
<div class='divider'> </div>
<div id="no-captures" hidden>
<h2>No captures available. Create new ones via Capture tab.</h2>
</div>
<div id='have-captures'>
<div class='column'>
<h2>Capture:</h2>
<select id='playback-name' class='indent name-combo'>
<option value='' disabled selected>Choose a capture</option>
</select>
<div id='playback-details' class='gapped'>
<div class='column gapped'>
<h2>URLs:</h2>
<textarea id='playback-urls' class='indent url-list'>
</textarea>
</div>
<h2>Playback&nbsp;Options:</h2>
<div class='row gapped'>
<div class='indent sub-label'>Repetitions:</div>
<input id='playback-repeats' type='number' min='1' max='100'
value='1'/>
</div>
<div class='row gapped'>
<div class='indent sub-label'>Install&nbsp;Extension:</div>
<input id='playback-extension' type='text' />
<button id='playback-browse'>Browse..</button>
</div>
<div class='row gapped'>
<button id='do-playback'>Playback</button>
<button id='do-delete'>Delete</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id='popup' hidden>
<div id='popup-box' style='column'>
<div id='popup-content'>
When in the course of human events it<br/>
becomes necessary for one people<br/>
to dissolve...
</div>
<button id='do-popup-dismiss' class='gapped'>Dismiss</button>
</div>
</div>
</div>
<script type='text/javascript' src='cycler_data.js'></script>
<script type='text/javascript' src='capture_tab.js'></script>
<script type='text/javascript' src='playback_tab.js'></script>
<script type='text/javascript' src='cycler_ui.js'></script>
</body>
</html>
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function $(criterion) {
return document.querySelector(criterion);
}
var cyclerUI = new (function () {
this.urlList = [];
this.cacheDir = "";
this.captureTab = $("#capture-tab");
this.captureTabLabel = $("#capture-tab-label");
this.captureButton = $("#capture-test");
this.captureErrorDiv = $("#capture-errors-display");
this.captureErrorList = $("#capture-errors");
this.playbackTab = $("#playback-tab");
this.playbackTabLabel = $("#playback-tab-label");
this.playbackURLs = $("#playback-urls");
this.playbackCache = $("#playback-cache-dir");
this.playbackButton = $("#playback-test");
this.playbackErrorDiv = $("#playback-errors-display");
this.playbackErrorList = $("#playback-errors");
this.playbackURLs.innerText = this.playbackCache.innerText = noTestMessage;
this.enableTab = function(tabLabel, tab) {
var tabList = document.querySelectorAll(".tab");
var tabLabelList = document.querySelectorAll(".tab-label");
for (var i = 0; i < tabList.length; i++)
if (tabList[i] == tab)
tabList[i].style.visibility = "visible";
else
tabList[i].style.visibility = "hidden";
for (var i = 0; i < tabLabelList.length; i++)
if (tabLabelList[i] == tabLabel) {
tabLabelList[i].classList.add("enabled-tab-label");
tabLabelList[i].classList.remove("disabled-tab-label");
} else {
tabLabelList[i].classList.remove("enabled-tab-label");
tabLabelList[i].classList.add("disabled-tab-label");
}
}
this.chooseCapture = function() {
this.enableTab(this.captureTabLabel, this.captureTab);
}
this.chooseReplay = function() {
this.enableTab(this.playbackTabLabel, this.playbackTab);
}
this.captureTest = function() {
var errorList = $("#capture-errors");
var errors = [];
this.cacheDir = $("#capture-cache-dir").value;
this.urlList = $("#capture-urls").value.split("\n");
if (errors.length > 0) {
this.captureErrorList.innerText = errors.join("\n");
this.captureErrorDiv.className = "error-list-show";
}
else {
this.captureErrorDiv.className = "error-list-hide";
this.captureButton.disabled = true;
chrome.experimental.record.captureURLs(this.urlList, this.cacheDir,
this.onCaptureDone.bind(this));
}
}
this.onCaptureDone = function(errors) {
this.captureButton.disabled = false;
if (errors.length > 0) {
this.captureErrorList.innerText = errors.join("\n");
this.captureErrorDiv.className = "error-list-show";
this.playbackButton.disabled = true;
this.playbackCache.innerText = this.playbackURLs.innerText =
noTestMessage;
}
else {
this.captureErrorDiv.className = "error-list-hide";
this.playbackButton.disabled = false;
this.playbackURLs.innerText = this.urlList.join("\n");
this.playbackCache.innerText = this.cacheDir;
}
}
this.playbackTest = function() {
var extensionPath = $("#extension-dir").value;
var repeatCount = parseInt($('#repeat-count').value);
var errors = [];
// Check local errors
if (isNaN(repeatCount))
errors.push("Enter a number for repeat count");
else if (repeatCount < 1 || repeatCount > 100)
errors.push("Repeat count must be between 1 and 100");
if (errors.length > 0) {
this.playbackErrorList.innerText = errors.join("\n");
this.playbackErrorDiv.className = "error-list-show";
} else {
this.playbackErrorDiv.className = "error-list-hide";
this.playbackButton.disabled = true;
chrome.experimental.record.playbackURLs(
this.urlList,
this.cacheDir,
repeatCount,
{"extensionPath": extensionPath},
this.onReplayDone.bind(this));
}
}
this.onReplayDone = function(result) {
var playbackResult = $("#playback-result");
this.playbackButton.disabled = false;
if (result.errors.length > 0) {
this.playbackErrorList.innerText = result.errors.join("<br>");
this.playbackErrorDiv.className = "error-list-show";
}
else {
this.playbackErrorDiv.className = "error-list-hide";
playbackResult.innerText = "Test took " + result.runTime + "mS :\n" +
result.stats;
}
}
this.captureButton.addEventListener("click", this.captureTest.bind(this));
this.playbackButton.addEventListener("click", this.playbackTest.bind(this));
this.captureTabLabel.addEventListener("click", this.chooseCapture.bind(this));
this.playbackTabLabel.addEventListener("click", this.chooseReplay.bind(this));
this.enableTab(this.captureTabLabel, this.captureTab);
})();
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var CyclerData = function () {
this.currentCaptures_ = ['alpha', 'beta', 'gamma'];
/**
* Mark a capture as saved successfully. Actual writing of the cache
* directory and URL list into the FileSystem is done from the C++ side.
* JS side just updates the capture choices.
* @param {!string} name The name of the capture.
* TODO(cstaley): Implement actual addition of new capture data
*/
this.saveCapture = function(name) {
console.log('Saving capture ' + name);
this.currentCaptures_.push(name);
}
/**
* Return a list of currently stored captures in the local FileSystem.
* @return {Array.<!string>} Names of all the current captures.
* TODO(cstaley): Implement actual generation of current capture list via
* ls-like traversal of the extension's FileSystem.
*/
this.getCaptures = function() {
return this.currentCaptures_;
}
/**
* Delete capture |name| from the local FileSystem, and update the
* capture choices HTML select element.
* @param {!string} name The name of the capture to delete.
* TODO(cstaley): Implement actual deletion
*/
this.deleteCapture = function(name) {
this.currentCaptures_.splice(this.currentCaptures_.indexOf(name), 1);
}
};
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function $(criterion) {
return document.querySelector(criterion);
}
var cyclerUI = new (function () {
/**
* Enum for different UI states.
* @enum {number}
* @private
*/
var EnableState_ = {capture: 0, playback: 1};
this.cyclerData_ = new CyclerData();
// Members for all UI elements subject to programmatic adjustment.
this.captureTabLabel_ = $('#capture-tab-label');
this.playbackTabLabel_ = $('#playback-tab-label');
this.playbackTab_ = new PlaybackTab(this, this.cyclerData_);
this.captureTab_ = new CaptureTab(this, this.cyclerData_, this.playbackTab_);
this.popupDialog_ = $('#popup');
this.popupContent_ = $('#popup-content');
this.doPopupDismiss_ = $('#do-popup-dismiss');
/**
* Name of the most recent capture made, or the one most recently chosen
* for playback.
* @type {!string}
*/
this.currentCaptureName = null;
/**
* One of the EnableState_ values, showing which tab is presently
* enabled.
* @type {number}
*/
this.enableState = null;
/*
* Enable the capture tab, changing tab labels approproiately.
* @private
*/
this.enableCapture_ = function() {
if (this.enableState != EnableState_.capture) {
this.enableState = EnableState_.capture;
this.captureTab_.enable();
this.playbackTab_.disable();
}
};
/*
* Enable the playback tab, changing tab labels approproiately.
* @private
*/
this.enablePlayback_ = function() {
if (this.enableState != EnableState_.playback) {
this.enableState = EnableState_.playback;
this.captureTab_.disable();
this.playbackTab_.enable();
}
};
/**
* Show an overlay with a message, a dismiss button with configurable
* label, and an action to call upon dismissal.
* @param {!string} content The message to display.
* @param {!string} dismissLabel The label on the dismiss button.
* @param {function()} action Additional action to take, if any, upon
* dismissal.
*/
this.showMessage = function(content, dismissLabel, action) {
this.popupContent_.innerText = content;
this.doPopupDismiss_.innerText = dismissLabel;
this.popupDialog_.hidden = false;
if (action != null)
doPopupDismiss_.addEventListener('click', action);
}
/**
* Default action for popup dismissal button, performed in addition to
* any other actions that may be specified in showMessage_ call.
* @private
*/
this.clearMessage_ = function() {
this.popupDialog_.hidden = true;
}
// Set up listeners on all buttons.
this.doPopupDismiss_.addEventListener('click', this.clearMessage_.bind(this));
// Set up listeners on tab labels.
this.captureTabLabel_.addEventListener('click',
this.enableCapture_.bind(this));
this.playbackTabLabel_.addEventListener('click',
this.enablePlayback_.bind(this));
// Start with capture tab displayed.
this.enableCapture_();
})();
{
"manifest_version": 2,
"name": "Cycler",
"version": "0.1",
"description": "Cycler UI",
"app": {
"launch": {
"local_path": "cycler.html"
}
},
"icons": {
"128": "cycler_icon_128.png",
"16": "cycler_icon_16.png"
},
"permissions": [
"experimental"
]
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Constructor for the tab UI governing playback selection and running.
* All HTML controls under tag #plaback-tab, plus the tab label
* #playback-tab-label are controlled by this class.
* @param {!Object} cyclerUI The master UI class, needed for global state
* such as current capture name.
* @param {!Object} cyclerData The local FileSystem-based database of
* available captures to play back.
*/
var PlaybackTab = function (cyclerUI, cyclerData) {
/**
* Enum for different playback tab states.
* @enum {number}
* @private
*/
var EnableState_ = {
choosePlayback: 1, // Choose a playback, if none already chosen.
showPlayback: 2, // Show currently chosen playback, and offer options.
showNoCaptures: 3 // Show message indicating no captures are available.
};
this.cyclerUI_ = cyclerUI;
this.cyclerData_ = cyclerData;
/**
* Create members for all UI elements with which we'll interact.
*/
this.tabLabel_ = $('#playback-tab-label');
this.playbackTab_ = $('#playback-tab');
this.noCaptures_ = $('#no-captures');
this.yesCaptures_ = $('#have-captures');
this.playbackName_ = $('#playback-name');
this.playbackDetails_ = $('#playback-details');
this.playbackURLs_ = $('#playback-urls');
this.playbackRepeats_ = $('#playback-repeats');
this.playbackExtension_ = $('#playback-extension');
this.doPlaybackButton_ = $('#do-playback');
this.doDeleteButton_ = $('#do-delete');
/*
* Enable the playback tab, showing no current playback choice, by
* hiding the playbackDetails_ div.
* @private
*/
this.enableChoosePlayback_ = function() {
if (this.enableState != EnableState_.choosePlayback) {
this.enableState = EnableState_.choosePlayback;
this.yesCaptures_.hidden = false;
this.noCaptures_.hidden = true;
this.playbackDetails_.hidden = true;
}
};
/*
* Enable the playback tab, showing a current playback choice by showing
* the playbackDetails_ div.
* @private
*/
this.enableShowPlayback_ = function() {
if (this.enableState != EnableState_.showPlayback) {
this.enableState = EnableState_.showPlayback;
this.yesCaptures_.hidden = false;
this.noCaptures_.hidden = true;
this.playbackDetails_.hidden = false;
}
};
/*
* Enable the playback tab and adjust tab labels appropriately. Show
* no available captures by hiding the yesCaptures_ div and showing the
* noCaptures_ div instead.
* @private
*/
this.enableShowNoCaptures_ = function() {
if (this.enableState != EnableState_.showNoCaptures) {
this.enableState = EnableState_.showNoCaptures;
this.noCaptures_.hidden = false;
this.yesCaptures_.hidden = true;
}
};
/**
* Enable the playback tab, showing either its "no captures", "choose
* a capture" or "display chosen capture" form, depending on the state
* of existing captures and |currentCaptureName_|.
*/
this.enable = function() {
this.tabLabel_.classList.add('selected');
this.updatePlaybackChoices_();
this.playbackTab_.hidden = false;
if (this.cyclerData_.getCaptures().length == 0) {
this.enableShowNoCaptures_();
} else if (this.cyclerUI_.currentCaptureName == null) {
this.enableChoosePlayback_();
} else {
this.enableShowPlayback_();
}
}
/**
* Disable the playback tab altogether, presumably in favor of some
* other tab.
*/
this.disable = function() {
this.tabLabel_.classList.remove('selected');
this.playbackTab_.hidden = true;
}
/**
* Utility function to refresh the selection list of captures that may
* be chosen for playback. Show all current captures, and also a
* "Choose a capture" default text if no capture is currently selected.
* @private
*/
this.updatePlaybackChoices_ = function() {
var captureNames = this.cyclerData_.getCaptures();
var options = this.playbackName_.options;
var nextIndex = 0;
var chooseOption;
options.length = 0;
if (this.cyclerUI_.currentCaptureName == null) {
chooseOption = new Option('Choose a capture', null);
chooseOption.disabled = true;
options.add(chooseOption);
options.selectedIndex = nextIndex++;
}
for (var i = 0; i < captureNames.length; i++) {
options.add(new Option(captureNames[i], captureNames[i]));
if (captureNames[i] == this.cyclerUI_.currentCaptureName) {
options.selectedIndex = nextIndex;
}
nextIndex++;
}
}
/**
* Event callback for selection of a capture to play back. Save the
* choice in |currentCaptureName_|. Update the selection list because the
* default reminder message is no longer needed when a capture is chosen.
* Change playback tab to show-chosen-capture mode.
*/
this.selectPlaybackName = function() {
this.cyclerUI_.currentCaptureName = this.playbackName_.value;
this.updatePlaybackChoices_();
this.enableShowPlayback_();
}
/**
* Event callback for pressing the playback button, which button is only
* enabled if a capture has been chosen for playback. Check for errors
* on playback page, and either display a message with such errors, or
* call the extenion api to initiate a playback.
* @private
*/
this.doPlayback_ = function() {
var extensionPath = this.playbackExtension_.value;
var repeatCount = parseInt(this.playbackRepeats_.value);
var errors = [];
// Check local errors
if (isNaN(repeatCount)) {
errors.push('Enter a number for repeat count');
} else if (repeatCount < 1 || repeatCount > 100) {
errors.push('Repeat count must be between 1 and 100');
}
if (errors.length > 0) {
this.cyclerUI_.showMessage(errors.join('\n'), 'Ok');
} else {
this.doPlaybackButton_.disabled = true;
chrome.experimental.record.replayURLs(
this.cyclerUI_.currentCaptureName,
repeatCount,
{'extensionPath': extensionPath},
this.onPlaybackDone.bind(this));
}
}
/**
* Extension API calls this back when a playback is done.
* @param {!{
* runTime: number,
* stats: string,
* errors: !Array.<string>
* }} results The results of the playback, including running time in ms,
* a string of statistics information, and a string array of errors.
*/
this.onPlaybackDone = function(results) {
this.doPlaybackButton_.disabled = false;
if (results.errors.length > 0) {
this.cyclerUI_.showMessage(results.errors.join('\n'), 'Ok');
} else {
this.cyclerUI_.showMessage('Test took ' + results.runTime + 'mS :\n' +
results.stats, 'Ok');
}
}
/**
* Delete the capture with name |currentCaptureName_|. For this method
* to be callable, there must be a selected currentCaptureName_. Change
* the displayed HTML to show no captures if none exist now, or to show
* none selected (since we just deleted the selected one).
* @private
*/
this.doDelete_ = function() {
this.cyclerData_.deleteCapture(this.cyclerUI_.currentCaptureName);
this.cyclerUI_.currentCaptureName = null;
this.updatePlaybackChoices_();
if (this.cyclerData_.getCaptures().length == 0) {
this.enableShowNoCaptures_();
} else {
this.enableChoosePlayback_();
}
}
// Set up listeners on buttons.
this.doPlaybackButton_.addEventListener('click', this.doPlayback_.bind(this));
this.doDeleteButton_.addEventListener('click', this.doDelete_.bind(this));
// Set up initial selection list for existing captures, and selection
// event listener.
this.updatePlaybackChoices_();
this.playbackName_.addEventListener('change',
this.selectPlaybackName.bind(this));
};
{{+partials.standard_apps_api api:apis.experimental_record}}
{{+partials.standard_extensions_api api:apis.experimental_record}}
...@@ -16984,7 +16984,7 @@ other types of suffix sets. ...@@ -16984,7 +16984,7 @@ other types of suffix sets.
<int value="178" label="BOOKMARKS_GETTREE"/> <int value="178" label="BOOKMARKS_GETTREE"/>
<int value="179" label="FILEBROWSERPRIVATE_SELECTFILES"/> <int value="179" label="FILEBROWSERPRIVATE_SELECTFILES"/>
<int value="180" label="RUNTIME_GETBACKGROUNDPAGE"/> <int value="180" label="RUNTIME_GETBACKGROUNDPAGE"/>
<int value="181" label="EXPERIMENTAL_RECORD_REPLAYURLS"/> <int value="181" label="DELETED_EXPERIMENTAL_RECORD_REPLAYURLS"/>
<int value="182" label="WEBSTOREPRIVATE_COMPLETEINSTALL"/> <int value="182" label="WEBSTOREPRIVATE_COMPLETEINSTALL"/>
<int value="183" label="DELETED_EXPERIMENTAL_SPEECHINPUT_START"/> <int value="183" label="DELETED_EXPERIMENTAL_SPEECHINPUT_START"/>
<int value="184" label="COOKIES_GETALL"/> <int value="184" label="COOKIES_GETALL"/>
...@@ -17154,7 +17154,7 @@ other types of suffix sets. ...@@ -17154,7 +17154,7 @@ other types of suffix sets.
<int value="348" label="CONTEXTMENUS_CREATE"/> <int value="348" label="CONTEXTMENUS_CREATE"/>
<int value="349" label="MEDIAPLAYERPRIVATE_GETPLAYLIST"/> <int value="349" label="MEDIAPLAYERPRIVATE_GETPLAYLIST"/>
<int value="350" label="DOWNLOADS_ERASE"/> <int value="350" label="DOWNLOADS_ERASE"/>
<int value="351" label="EXPERIMENTAL_RECORD_CAPTUREURLS"/> <int value="351" label="DELETED_EXPERIMENTAL_RECORD_CAPTUREURLS"/>
<int value="352" label="TTS_ISSPEAKING"/> <int value="352" label="TTS_ISSPEAKING"/>
<int value="353" label="BOOKMARKS_REMOVETREE"/> <int value="353" label="BOOKMARKS_REMOVETREE"/>
<int value="354" label="FILEBROWSERPRIVATE_SEARCHDRIVE"/> <int value="354" label="FILEBROWSERPRIVATE_SEARCHDRIVE"/>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment