// Original path: bite-project/tools/rpf/extension/src/libs/recordmanager.js (Google BITE, Apache 2.0, copied unmodified below this line, for reading only, not built) // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview This file contains the recording manager, * which has functions to communicate with the page under * record, for example: init page, start recording, etc.. * * @author phu@google.com (Po Hu) */ goog.provide('rpf.RecordManager'); goog.require('rpf.ScriptManager'); /** * A class for managing the recorder part of RPF. * @param {rpf.ScriptManager} scriptMgr The script manager. * @param {function()} initAllListeners To init all the listeners. * @param {function()} removeAllListeners To remove all the listeners. * @param {function()} addTestTabRemovedListener To add the test tab removed * listener. * @param {function(Object, function(*)=)} sendMessageToConsole The * function to send message to console world. * @param {function(Array, number, boolean, number, function()=)} * executeMultipleScripts The function to execute multiple scripts. * @constructor * @export */ rpf.RecordManager = function(scriptMgr, initAllListeners, removeAllListeners, addTestTabRemovedListener, sendMessageToConsole, executeMultipleScripts) { /** * The script manager. * @type {rpf.ScriptManager} * @private */ this.scriptMgr_ = scriptMgr; /** * To init all the listeners. * @type {function()} * @private */ this.initAllListeners_ = initAllListeners; /** * To remove all the listeners. * @type {function()} * @private */ this.removeAllListeners_ = removeAllListeners; /** * To add the test tab removed listener. * @type {function()} * @private */ this.addTestTabRemovedListener_ = addTestTabRemovedListener; /** * The function to send message to console world. * @type {function(Object, function(*)=)} * @private */ this.sendMessageToConsole_ = sendMessageToConsole; /** * The function to send message to console world. * @type {function(Array, number, boolean, number, function()=)} * @private */ this.executeMultipleScripts_ = executeMultipleScripts; /** * Whether the page has been initialized. * @type {boolean} * @private */ this.pageInitialized_ = false; /** * The tab id of the page under test. * @type {number} * @private */ this.testTabId_ = -1; /** * The window id of the page under test. * @type {number} * @private */ this.testWindowId_ = 0; /** * The latest recorded event type. * @type {string} * @private */ this.latestEvent_ = ''; /** * Whether it is recording. * @type {boolean} * @private */ this.isRecording_ = false; /** * The recording mode. * @type {string} * @private */ this.recordingMode_ = ''; /** * The recording info. * @type {Object} * @private */ this.recordingInfo_ = {}; }; /** * @const The user action related JS lib. * @type {string} * @private */ rpf.RecordManager.GET_ACTION_INFO_JS_ = 'getactioninfo_script.js'; /** * @const The element's descriptive info related JS lib. * @type {string} * @export */ rpf.RecordManager.ELEMENT_HELPER = 'elementhelper_script.js'; /** * Starts recording. * @param {Object=} opt_info The optional recording config info. * @param {number=} opt_tabId The tab id. * @param {number=} opt_windowId The window id. * @return {boolean} Whether it successfully started recording. */ rpf.RecordManager.prototype.startRecording = function( opt_info, opt_tabId, opt_windowId) { var info = opt_info || {}; if (opt_tabId && opt_windowId) { this.setRecordingTab(opt_tabId, opt_windowId); } this.startRecording_(info); return true; }; /** * Callback after checking whether the tab under record exists. * @param {Function} callback The callback function. * @param {Object} injectedTabs The injected tabs. * @param {Tab} tab The tab. * @private */ rpf.RecordManager.prototype.checkTestTabExistsCallback_ = function( callback, injectedTabs, tab) { var result = {}; if (!tab) { result['success'] = false; result['message'] = 'The page under record is missing. ' + 'For more details, please check ' + 'here.'; } else { if (injectedTabs[tab.id]) { result['success'] = true; result['url'] = tab.url; this.scriptMgr_.startUrl = tab.url; } else { result['success'] = false; result['message'] = 'Maybe you would need to refresh the page' + ' under record. ' + 'For more details, please check ' + 'here.'; } } callback(result); }; /** * Checks whether the tab under record exists. * @param {Function} callback The callback function. * @param {Object} tabs The tabs that have injection. */ rpf.RecordManager.prototype.checkTestTabExists = function(callback, tabs) { chrome.tabs.get( this.testTabId_, goog.bind(this.checkTestTabExistsCallback_, this, callback, tabs)); }; /** * Starts recording. * @param {Object} info The recording config info. * @private */ rpf.RecordManager.prototype.startRecording_ = function(info) { this.recordingMode_ = 'rpf'; this.recordingInfo_ = info; this.initAllListeners_(); this.isRecording_ = true; this.highlightRecordTab(); this.executePageInit(goog.bind(this.startRecordingInPage, this)); }; /** * Enum for commands will be run in page. * @enum {string} * @export */ rpf.RecordManager.CmdCode = { CLEAR: 'recordHelper.stopRecording();', BLOCK_MODE: 'recordHelper.validator_.setWaitBlockElemMode();', ELEMS_IN_BLOCK_MODE: 'recordHelper.validator_.setWaitElemsInBlockMode();', CLEAR_BLOCK_MODE: 'recordHelper.validator_.clearBlockMode();', START: 'recordHelper.startRecording();' }; /** * Clears the listeners inside the page under test. * @param {rpf.RecordManager.CmdCode|string} cmd * The command that will be run in page. * @param {boolean=} opt_allFrame Whether to execute the command in all frames. * @export */ rpf.RecordManager.prototype.runCode = function(cmd, opt_allFrame) { var allFrame = opt_allFrame || false; var wrappedCmd = 'try {' + cmd + '} catch(e) {console.log("The cmd was not found.")}'; chrome.tabs.executeScript( this.testTabId_, {code: wrappedCmd, allFrames: allFrame}); }; /** * Stops recording. * @export */ rpf.RecordManager.prototype.stopRecording = function() { this.isRecording_ = false; this.removeAllListeners_(); if (this.testTabId_ > 0) { chrome.tabs.sendRequest( this.testTabId_, {recordAction: Bite.Constants.RECORD_ACTION.STOP_RECORDING, params: {}}); } }; /** * Sets the tab and window ids of the page under test. * @param {number=} opt_tabId The sender tab id. * @param {number=} opt_windowId The sender window id. * @param {Function=} opt_callback The callback function. * @export */ rpf.RecordManager.prototype.setRecordingTab = function( opt_tabId, opt_windowId, opt_callback) { var callback = opt_callback || goog.nullFunction; if (opt_tabId && opt_windowId) { this.setStatusBeforeRecording_(opt_tabId, opt_windowId, callback); } else { chrome.tabs.getSelected( null, goog.bind(this.callbackSetRecordingTab_, this, callback)); } }; /** * Sets the tab and window ids. * @param {Function} callback The callback function. * @param {Object} tab Chrome tab object. * @private */ rpf.RecordManager.prototype.callbackSetRecordingTab_ = function(callback, tab) { this.checkUrlMatching_(tab.url); this.setStatusBeforeRecording_(tab.id, tab.windowId, callback); }; /** * Warns users if the URL is not matching. * @param {string} url The tab under record's url. * @private */ rpf.RecordManager.prototype.checkUrlMatching_ = function(url) { // To avoid making users confused about why pages are not recordable, we // have to warn them if the current URL is not allowed. if (goog.string.contains(url, 'chrome.google.com/webstore') || goog.string.startsWith(url, 'chrome://') || goog.string.startsWith(url, 'about:')) { alert('The selected page does not allow content script. ' + 'For example, blank page, webstore page, other non-http(s) pages. ' + 'Please select a valid page to record. For more details,' + ' please refer to https://sites.google.com/site' + '/rpfwiki/q-a#cannotrecord'); } }; /** * Sets status and to prepare recording. * @param {number} tabId The sender tab id. * @param {number} windowId The sender window id. * @param {Function} callback The callback function. * @private */ rpf.RecordManager.prototype.setStatusBeforeRecording_ = function( tabId, windowId, callback) { this.testTabId_ = tabId; this.testWindowId_ = windowId; this.addTestTabRemovedListener_(); callback(); }; /** * Brings the tab under record foreground. * @param {function()=} opt_callback An optional callback function. * @export */ rpf.RecordManager.prototype.highlightRecordTab = function(opt_callback) { var callback = opt_callback || callback; var tabCallback = /** @type {function(Tab)} */ (callback); var windowCallback = /** @type {function()} */ (callback); chrome.tabs.update(this.testTabId_, { selected: true }, tabCallback); chrome.windows.update(this.testWindowId_, { focused: true }, windowCallback); }; /** * Enters updater mode. * @export */ rpf.RecordManager.prototype.enterUpdaterMode = function() { this.recordingMode_ = 'updater'; this.highlightRecordTab(); chrome.tabs.executeScript(this.testTabId_, {code: 'recordHelper.enterUpdaterMode();'}); }; /** * Opens the xpath finder in page. * @export */ rpf.RecordManager.prototype.openXpathFinder = function() { this.highlightRecordTab(); chrome.tabs.sendRequest( this.testTabId_, {recordAction: Bite.Constants.RECORD_ACTION.OPEN_XPATH_FINDER}); }; /** * Ends updater mode. * @export */ rpf.RecordManager.prototype.endUpdaterMode = function() { chrome.tabs.executeScript(this.testTabId_, {code: 'recordHelper.endUpdaterMode();'}); }; /** * Tests a locator. * @param {Array} locators The locator array. * @param {function(Object)} callback The callback function. * @export */ rpf.RecordManager.prototype.testLocator = function(locators, callback) { chrome.tabs.sendRequest( this.testTabId_, {command: 'testLocator', locators: locators}, /** @type {function(*)} */ (callback)); }; /** * Tests a descriptor in the page under test. * @param {Object} descriptor A descriptor object for an element. * @private */ rpf.RecordManager.prototype.testDescriptor_ = function(descriptor) { this.highlightRecordTab(); chrome.tabs.executeScript(this.testTabId_, {code: 'recordHelper.outlineElems_(' + descriptor + ');'}); }; /** * Tests a descriptor in the page under test. * @param {Object} descriptor A descriptor object for an element. * @export */ rpf.RecordManager.prototype.testDescriptor = function(descriptor) { var descStr = JSON.stringify(descriptor); console.log('Test descriptor in page!'); this.executePageInit(goog.bind(this.testDescriptor_, this, descStr)); }; /** * Tests block validation in the page under test. * @param {Object} blockObj An object of block info. * @export */ rpf.RecordManager.prototype.testBlockValidation = function(blockObj) { var blockStr = JSON.stringify(blockObj); chrome.tabs.sendRequest( this.testTabId_, {command: 'testValidateBlock', descriptor: blockStr}, function(response) { if (response.result != Bite.Constants.WorkerResults.PASS) { console.log('Testing Error:' + response.result); } }); }; /** * Gets the root array. * @return {Array} The root element array. * @private */ rpf.RecordManager.prototype.getRoots_ = function() { var results = []; if (this.recordingInfo_ && this.recordingInfo_['pageMap']) { var pageMap = this.recordingInfo_['pageMap']; if (typeof pageMap == 'string') { pageMap = goog.json.parse(pageMap); } for (var item in pageMap) { results.push( {'xpath': item, 'className': pageMap[item]}); } } return results; }; /** * Starts recording in content script. * @export */ rpf.RecordManager.prototype.startRecordingInPage = function() { if (this.recordingMode_ == 'updater') { this.enterUpdaterMode(); } else { var roots = this.getRoots_(); chrome.tabs.sendRequest( this.testTabId_, {recordAction: Bite.Constants.RECORD_ACTION.START_RECORDING, params: {'rootArr': roots, 'xpathFinderOn': this.recordingInfo_['xpathFinderOn']}}); } }; /** * Executes three content scripts to initialize recording on a page. * @param {function()=} opt_callback An optional callback function. * @export */ rpf.RecordManager.prototype.executePageInit = function(opt_callback) { if (this.pageInitialized_) { this.pageInitialized_ = false; this.runCode(rpf.RecordManager.CmdCode.CLEAR, true); } this.pageInitialized_ = true; if (opt_callback) { opt_callback(); } }; /** * @return {boolean} Whether is recording. * @export */ rpf.RecordManager.prototype.isRecording = function() { return this.isRecording_; }; /** * @return {number} The tab id of the page under test. * @export */ rpf.RecordManager.prototype.getTestTabId = function() { return this.testTabId_; }; /** * @param {number} tabId The tab id of the page under test. * @export */ rpf.RecordManager.prototype.setTestTabId = function(tabId) { this.testTabId_ = tabId; }; /** * @return {number} The tab id of the page under test. * @export */ rpf.RecordManager.prototype.getTestWindowId = function() { return this.testWindowId_; }; /** * @param {number} windowId The tab id of the page under test. * @export */ rpf.RecordManager.prototype.setTestWindowId = function(windowId) { this.testWindowId_ = windowId; };