// Copyright (c) 2011 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. // This script contains privileged chrome extension related javascript APIs. // It is loaded by pages whose URL has the chrome-extension protocol. var chrome = chrome || {}; (function() { native function GetExtensionAPIDefinition(); native function StartRequest(); native function GetChromeHidden(); native function GetNextRequestId(); native function Print(); native function GetCurrentPageActions(extensionId); native function GetExtensionViews(); native function GetNextContextMenuId(); native function GetNextTtsEventId(); native function OpenChannelToTab(); native function GetRenderViewId(); native function SetIconCommon(); native function GetUniqueSubEventName(eventName); native function GetLocalFileSystem(name, path); native function DecodeJPEG(jpeg_image); var chromeHidden = GetChromeHidden(); if (!chrome) chrome = {}; function forEach(dict, f) { for (key in dict) { if (dict.hasOwnProperty(key)) f(key, dict[key]); } } // Validate arguments. chromeHidden.validationTypes = []; chromeHidden.validate = function(args, schemas) { if (args.length > schemas.length) throw new Error("Too many arguments."); for (var i = 0; i < schemas.length; i++) { if (i in args && args[i] !== null && args[i] !== undefined) { var validator = new chromeHidden.JSONSchemaValidator(); validator.addTypes(chromeHidden.validationTypes); validator.validate(args[i], schemas[i]); if (validator.errors.length == 0) continue; var message = "Invalid value for argument " + (i + 1) + ". "; for (var i = 0, err; err = validator.errors[i]; i++) { if (err.path) { message += "Property '" + err.path + "': "; } message += err.message; message = message.substring(0, message.length - 1); message += ", "; } message = message.substring(0, message.length - 2); message += "."; throw new Error(message); } else if (!schemas[i].optional) { throw new Error("Parameter " + (i + 1) + " is required."); } } }; // Callback handling. var requests = []; chromeHidden.handleResponse = function(requestId, name, success, response, error) { try { var request = requests[requestId]; if (success) { delete chrome.extension.lastError; } else { if (!error) { error = "Unknown error."; } console.error("Error during " + name + ": " + error); chrome.extension.lastError = { "message": error }; } if (request.customCallback) { request.customCallback(name, request, response); } if (request.callback) { // Callbacks currently only support one callback argument. var callbackArgs = response ? [chromeHidden.JSON.parse(response)] : []; // Validate callback in debug only -- and only when the // caller has provided a callback. Implementations of api // calls my not return data if they observe the caller // has not provided a callback. if (chromeHidden.validateCallbacks && !error) { try { if (!request.callbackSchema.parameters) { throw "No callback schemas defined"; } if (request.callbackSchema.parameters.length > 1) { throw "Callbacks may only define one parameter"; } chromeHidden.validate(callbackArgs, request.callbackSchema.parameters); } catch (exception) { return "Callback validation error during " + name + " -- " + exception.stack; } } if (response) { request.callback(callbackArgs[0]); } else { request.callback(); } } } finally { delete requests[requestId]; delete chrome.extension.lastError; } return undefined; }; function prepareRequest(args, argSchemas) { var request = {}; var argCount = args.length; // Look for callback param. if (argSchemas.length > 0 && args.length == argSchemas.length && argSchemas[argSchemas.length - 1].type == "function") { request.callback = args[argSchemas.length - 1]; request.callbackSchema = argSchemas[argSchemas.length - 1]; --argCount; } request.args = []; for (var k = 0; k < argCount; k++) { request.args[k] = args[k]; } return request; } // Send an API request and optionally register a callback. // |opt_args| is an object with optional parameters as follows: // - noStringify: true if we should not stringify the request arguments. // - customCallback: a callback that should be called instead of the standard // callback. // - nativeFunction: the v8 native function to handle the request, or // StartRequest if missing. // - forIOThread: true if this function should be handled on the browser IO // thread. function sendRequest(functionName, args, argSchemas, opt_args) { if (!opt_args) opt_args = {}; var request = prepareRequest(args, argSchemas); if (opt_args.customCallback) { request.customCallback = opt_args.customCallback; } // JSON.stringify doesn't support a root object which is undefined. if (request.args === undefined) request.args = null; var sargs = opt_args.noStringify ? request.args : chromeHidden.JSON.stringify(request.args); var nativeFunction = opt_args.nativeFunction || StartRequest; var requestId = GetNextRequestId(); requests[requestId] = request; var hasCallback = (request.callback || opt_args.customCallback) ? true : false; return nativeFunction(functionName, sargs, requestId, hasCallback, opt_args.forIOThread); } // --- Setup additional api's not currently handled in common/extensions/api // WebRequestEvent object. This is used for special webRequest events with // extra parameters. Each invocation of addListener creates a new named // sub-event. That sub-event is associated with the extra parameters in the // browser process, so that only it is dispatched when the main event occurs // matching the extra parameters. // // Example: // chrome.webRequest.onBeforeRequest.addListener( // callback, {urls: "http://*.google.com/*"}); // ^ callback will only be called for onBeforeRequests matching the filter. chrome.WebRequestEvent = function(eventName, opt_argSchemas, opt_extraArgSchemas) { if (typeof eventName != "string") throw new Error("chrome.WebRequestEvent requires an event name."); this.eventName_ = eventName; this.argSchemas_ = opt_argSchemas; this.extraArgSchemas_ = opt_extraArgSchemas; this.subEvents_ = []; }; // Test if the given callback is registered for this event. chrome.WebRequestEvent.prototype.hasListener = function(cb) { return this.findListener_(cb) > -1; }; // Test if any callbacks are registered fur thus event. chrome.WebRequestEvent.prototype.hasListeners = function(cb) { return this.subEvents_.length > 0; }; // Registers a callback to be called when this event is dispatched. If // opt_filter is specified, then the callback is only called for events that // match the given filters. If opt_extraInfo is specified, the given optional // info is sent to the callback. chrome.WebRequestEvent.prototype.addListener = function(cb, opt_filter, opt_extraInfo) { var subEventName = GetUniqueSubEventName(this.eventName_); // Note: this could fail to validate, in which case we would not add the // subEvent listener. chromeHidden.validate(Array.prototype.slice.call(arguments, 1), this.extraArgSchemas_); chrome.experimental.webRequest.addEventListener( cb, opt_filter, opt_extraInfo, this.eventName_, subEventName); var subEvent = new chrome.Event(subEventName, this.argSchemas_); var subEventCallback = cb; if (opt_extraInfo && opt_extraInfo.indexOf("blocking") >= 0) { var eventName = this.eventName_; subEventCallback = function() { var requestId = arguments[0].requestId; try { var result = cb.apply(null, arguments); chrome.experimental.webRequest.eventHandled( eventName, subEventName, requestId, result); } catch (e) { chrome.experimental.webRequest.eventHandled( eventName, subEventName, requestId); throw e; } }; } this.subEvents_.push( {subEvent: subEvent, callback: cb, subEventCallback: subEventCallback}); subEvent.addListener(subEventCallback); }; // Unregisters a callback. chrome.WebRequestEvent.prototype.removeListener = function(cb) { var idx = this.findListener_(cb); if (idx < 0) { return; } var e = this.subEvents_[idx]; e.subEvent.removeListener(e.subEventCallback); if (e.subEvent.hasListeners()) { console.error( "Internal error: webRequest subEvent has orphaned listeners."); } this.subEvents_.splice(idx, 1); }; chrome.WebRequestEvent.prototype.findListener_ = function(cb) { for (var i in this.subEvents_) { var e = this.subEvents_[i]; if (e.callback === cb) { if (e.subEvent.findListener_(e.subEventCallback) > -1) return i; console.error("Internal error: webRequest subEvent has no callback."); } } return -1; }; function CustomBindingsObject() { } CustomBindingsObject.prototype.setSchema = function(schema) { // The functions in the schema are in list form, so we move them into a // dictionary for easier access. var self = this; self.parameters = {}; schema.functions.forEach(function(f) { self.parameters[f.name] = f.parameters; }); }; function extendSchema(schema) { var extendedSchema = schema.slice(); extendedSchema.unshift({'type': 'string'}); return extendedSchema; } var customBindings = {}; function setupChromeSetting() { function ChromeSetting(prefKey, valueSchema) { this.get = function(details, callback) { var getSchema = this.parameters.get; chromeHidden.validate([details, callback], getSchema); return sendRequest('types.ChromeSetting.get', [prefKey, details, callback], extendSchema(getSchema)); }; this.set = function(details, callback) { var setSchema = this.parameters.set.slice(); setSchema[0].properties.value = valueSchema; chromeHidden.validate([details, callback], setSchema); return sendRequest('types.ChromeSetting.set', [prefKey, details, callback], extendSchema(setSchema)); }; this.clear = function(details, callback) { var clearSchema = this.parameters.clear; chromeHidden.validate([details, callback], clearSchema); return sendRequest('types.ChromeSetting.clear', [prefKey, details, callback], extendSchema(clearSchema)); }; this.onChange = new chrome.Event('types.ChromeSetting.' + prefKey + '.onChange'); }; ChromeSetting.prototype = new CustomBindingsObject(); customBindings['ChromeSetting'] = ChromeSetting; } function setupContentSetting() { function ContentSetting(contentType, settingSchema) { this.get = function(details, callback) { var getSchema = this.parameters.get; chromeHidden.validate([details, callback], getSchema); return sendRequest('experimental.contentSettings.get', [contentType, details, callback], extendSchema(getSchema)); }; this.set = function(details, callback) { var setSchema = this.parameters.set.slice(); setSchema[0].properties.setting = settingSchema; chromeHidden.validate([details, callback], setSchema); return sendRequest('experimental.contentSettings.set', [contentType, details, callback], extendSchema(setSchema)); }; this.clear = function(details, callback) { var clearSchema = this.parameters.clear; chromeHidden.validate([details, callback], clearSchema); return sendRequest('experimental.contentSettings.clear', [contentType, details, callback], extendSchema(clearSchema)); }; this.getResourceIdentifiers = function(callback) { var schema = this.parameters.getResourceIdentifiers; chromeHidden.validate([callback], schema); return sendRequest( 'experimental.contentSettings.getResourceIdentifiers', [contentType, callback], extendSchema(schema)); }; } ContentSetting.prototype = new CustomBindingsObject(); customBindings['ContentSetting'] = ContentSetting; } function setupInputEvents() { chrome.experimental.input.onKeyEvent.dispatch = function(engineID, keyData) { var args = Array.prototype.slice.call(arguments); if (this.validate_) { var validationErrors = this.validate_(args); if (validationErrors) { chrome.experimental.input.eventHandled(requestId, false); return validationErrors; } } if (this.listeners_.length > 1) { console.error("Too many listeners for 'onKeyEvent': " + e.stack); chrome.experimental.input.eventHandled(requestId, false); return; } for (var i = 0; i < this.listeners_.length; i++) { try { var requestId = keyData.requestId; var result = this.listeners_[i].apply(null, args); chrome.experimental.input.eventHandled(requestId, result); } catch (e) { console.error("Error in event handler for 'onKeyEvent': " + e.stack); chrome.experimental.input.eventHandled(requestId, false); } } }; } // Page action events send (pageActionId, {tabId, tabUrl}). function setupPageActionEvents(extensionId) { var pageActions = GetCurrentPageActions(extensionId); var oldStyleEventName = "pageActions"; // TODO(EXTENSIONS_DEPRECATED): only one page action for (var i = 0; i < pageActions.length; ++i) { // Setup events for each extension_id/page_action_id string we find. chrome.pageActions[pageActions[i]] = new chrome.Event(oldStyleEventName); } } function setupToolstripEvents(renderViewId) { chrome.toolstrip = chrome.toolstrip || {}; chrome.toolstrip.onExpanded = new chrome.Event("toolstrip.onExpanded." + renderViewId); chrome.toolstrip.onCollapsed = new chrome.Event("toolstrip.onCollapsed." + renderViewId); } function setupHiddenContextMenuEvent(extensionId) { chromeHidden.contextMenus = {}; chromeHidden.contextMenus.handlers = {}; var eventName = "contextMenus"; chromeHidden.contextMenus.event = new chrome.Event(eventName); chromeHidden.contextMenus.ensureListenerSetup = function() { if (chromeHidden.contextMenus.listening) { return; } chromeHidden.contextMenus.listening = true; chromeHidden.contextMenus.event.addListener(function() { // An extension context menu item has been clicked on - fire the onclick // if there is one. var id = arguments[0].menuItemId; var onclick = chromeHidden.contextMenus.handlers[id]; if (onclick) { onclick.apply(null, arguments); } }); }; } // Parses the xml syntax supported by omnibox suggestion results. Returns an // object with two properties: 'description', which is just the text content, // and 'descriptionStyles', which is an array of style objects in a format // understood by the C++ backend. function parseOmniboxDescription(input) { var domParser = new DOMParser(); // The XML parser requires a single top-level element, but we want to // support things like 'hello, world!'. So we wrap the // provided text in generated root level element. var root = domParser.parseFromString( '' + input + '', 'text/xml'); // DOMParser has a terrible error reporting facility. Errors come out nested // inside the returned document. var error = root.querySelector('parsererror div'); if (error) { throw new Error(error.textContent); } // Otherwise, it's valid, so build up the result. var result = { description: '', descriptionStyles: [] }; // Recursively walk the tree. (function(node) { for (var i = 0, child; child = node.childNodes[i]; i++) { // Append text nodes to our description. if (child.nodeType == Node.TEXT_NODE) { result.description += child.nodeValue; continue; } // Process and descend into a subset of recognized tags. if (child.nodeType == Node.ELEMENT_NODE && (child.nodeName == 'dim' || child.nodeName == 'match' || child.nodeName == 'url')) { var style = { 'type': child.nodeName, 'offset': result.description.length }; result.descriptionStyles.push(style); arguments.callee(child); style.length = result.description.length - style.offset; continue; } // Descend into all other nodes, even if they are unrecognized, for // forward compat. arguments.callee(child); } })(root); return result; } function setupOmniboxEvents() { chrome.omnibox.onInputChanged.dispatch = function(text, requestId) { var suggestCallback = function(suggestions) { chrome.omnibox.sendSuggestions(requestId, suggestions); }; chrome.Event.prototype.dispatch.apply(this, [text, suggestCallback]); }; } function setupTtsEvents() { chromeHidden.tts = {}; chromeHidden.tts.handlers = {}; chrome.ttsEngine.onSpeak.dispatch = function(text, options, requestId) { var sendTtsEvent = function(event) { chrome.ttsEngine.sendTtsEvent(requestId, event); }; chrome.Event.prototype.dispatch.apply( this, [text, options, sendTtsEvent]); }; try { chrome.tts.onEvent.addListener( function(event) { var eventHandler = chromeHidden.tts.handlers[event.srcId]; if (eventHandler) { eventHandler({ type: event.type, charIndex: event.charIndex, errorMessage: event.errorMessage }); if (event.isFinalEvent) { delete chromeHidden.tts.handlers[event.srcId]; } } }); } catch (e) { // This extension doesn't have permission to access TTS, so we // can safely ignore this. } } // Get the platform from navigator.appVersion. function getPlatform() { var platforms = [ [/CrOS Touch/, "chromeos touch"], [/CrOS/, "chromeos"], [/Linux/, "linux"], [/Mac/, "mac"], [/Win/, "win"], ]; for (var i = 0; i < platforms.length; i++) { if (platforms[i][0].test(navigator.appVersion)) { return platforms[i][1]; } } return "unknown"; } chromeHidden.onLoad.addListener(function(extensionId, isExtensionProcess, isIncognitoProcess) { if (!isExtensionProcess) return; // Setup the ChromeSetting class so we can use it to construct // ChromeSetting objects from the API definition. setupChromeSetting(); // Setup the ContentSetting class so we can use it to construct // ContentSetting objects from the API definition. setupContentSetting(); // |apiFunctions| is a hash of name -> object that stores the // name & definition of the apiFunction. Custom handling of api functions // is implemented by adding a "handleRequest" function to the object. var apiFunctions = {}; // Read api definitions and setup api functions in the chrome namespace. // TODO(rafaelw): Consider defining a json schema for an api definition // and validating either here, in a unit_test or both. // TODO(rafaelw): Handle synchronous functions. // TOOD(rafaelw): Consider providing some convenient override points // for api functions that wish to insert themselves into the call. var apiDefinitions = chromeHidden.JSON.parse(GetExtensionAPIDefinition()); var platform = getPlatform(); apiDefinitions.forEach(function(apiDef) { // Check platform, if apiDef has platforms key. if (apiDef.platforms && apiDef.platforms.indexOf(platform) == -1) { return; } var module = chrome; var namespaces = apiDef.namespace.split('.'); for (var index = 0, name; name = namespaces[index]; index++) { module[name] = module[name] || {}; module = module[name]; } // Add types to global validationTypes if (apiDef.types) { apiDef.types.forEach(function(t) { chromeHidden.validationTypes.push(t); if (t.type == 'object' && customBindings[t.id]) { customBindings[t.id].prototype.setSchema(t); } }); } // Setup Functions. if (apiDef.functions) { apiDef.functions.forEach(function(functionDef) { // Module functions may have been defined earlier by hand. Don't // clobber them. if (module[functionDef.name]) return; var apiFunction = {}; apiFunction.definition = functionDef; apiFunction.name = apiDef.namespace + "." + functionDef.name; apiFunctions[apiFunction.name] = apiFunction; module[functionDef.name] = (function() { var args = arguments; if (this.updateArgumentsPreValidate) args = this.updateArgumentsPreValidate.apply(this, args); chromeHidden.validate(args, this.definition.parameters); if (this.updateArgumentsPostValidate) args = this.updateArgumentsPostValidate.apply(this, args); var retval; if (this.handleRequest) { retval = this.handleRequest.apply(this, args); } else { retval = sendRequest(this.name, args, this.definition.parameters, {customCallback: this.customCallback}); } // Validate return value if defined - only in debug. if (chromeHidden.validateCallbacks && chromeHidden.validate && this.definition.returns) { chromeHidden.validate([retval], [this.definition.returns]); } return retval; }).bind(apiFunction); }); } // Setup Events if (apiDef.events) { apiDef.events.forEach(function(eventDef) { // Module events may have been defined earlier by hand. Don't clobber // them. if (module[eventDef.name]) return; var eventName = apiDef.namespace + "." + eventDef.name; if (apiDef.namespace == "experimental.webRequest") { module[eventDef.name] = new chrome.WebRequestEvent(eventName, eventDef.parameters, eventDef.extraParameters); } else { module[eventDef.name] = new chrome.Event(eventName, eventDef.parameters); } }); } function addProperties(m, def) { // Parse any values defined for properties. if (def.properties) { forEach(def.properties, function(prop, property) { var value = property.value; if (value) { if (property.type === 'integer') { value = parseInt(value); } else if (property.type === 'boolean') { value = value === "true"; } else if (property["$ref"]) { var constructor = customBindings[property["$ref"]]; var args = value; // For an object property, |value| is an array of constructor // arguments, but we want to pass the arguments directly // (i.e. not as an array), so we have to fake calling |new| on // the constructor. value = { __proto__: constructor.prototype }; constructor.apply(value, args); } else if (property.type === 'object') { // Recursively add properties. addProperties(value, property); } else if (property.type !== 'string') { throw "NOT IMPLEMENTED (extension_api.json error): Cannot " + "parse values for type \"" + property.type + "\""; } } if (value) { m[prop] = value; } }); } } addProperties(module, apiDef); // getTabContentses is retained for backwards compatibility // See http://crbug.com/21433 chrome.extension.getTabContentses = chrome.extension.getExtensionTabs; }); apiFunctions["tabs.connect"].handleRequest = function(tabId, connectInfo) { var name = ""; if (connectInfo) { name = connectInfo.name || name; } var portId = OpenChannelToTab(tabId, chromeHidden.extensionId, name); return chromeHidden.Port.createPort(portId, name); }; apiFunctions["tabs.sendRequest"].handleRequest = function(tabId, request, responseCallback) { var port = chrome.tabs.connect(tabId, {name: chromeHidden.kRequestChannel}); port.postMessage(request); port.onDisconnect.addListener(function() { // For onDisconnects, we only notify the callback if there was an error. if (chrome.extension.lastError && responseCallback) responseCallback(); }); port.onMessage.addListener(function(response) { try { if (responseCallback) responseCallback(response); } finally { port.disconnect(); port = null; } }); }; apiFunctions["fileBrowserPrivate.requestLocalFileSystem"].customCallback = function(name, request, response) { var resp = response ? [chromeHidden.JSON.parse(response)] : []; var fs = null; if (!resp[0].error) fs = GetLocalFileSystem(resp[0].name, resp[0].path); if (request.callback) request.callback(fs); request.callback = null; }; apiFunctions["chromePrivate.decodeJPEG"].handleRequest = function(jpeg_image) { return DecodeJPEG(jpeg_image); }; apiFunctions["extension.getViews"].handleRequest = function(properties) { var windowId = -1; var type = "ALL"; if (typeof(properties) != "undefined") { if (typeof(properties.type) != "undefined") { type = properties.type; } if (typeof(properties.windowId) != "undefined") { windowId = properties.windowId; } } return GetExtensionViews(windowId, type) || null; }; apiFunctions["extension.getBackgroundPage"].handleRequest = function() { return GetExtensionViews(-1, "BACKGROUND")[0] || null; }; apiFunctions["extension.getToolstrips"].handleRequest = function(windowId) { if (typeof(windowId) == "undefined") windowId = -1; return GetExtensionViews(windowId, "TOOLSTRIP"); }; apiFunctions["extension.getExtensionTabs"].handleRequest = function(windowId) { if (typeof(windowId) == "undefined") windowId = -1; return GetExtensionViews(windowId, "TAB"); }; apiFunctions["devtools.getTabEvents"].handleRequest = function(tabId) { var tabIdProxy = {}; var functions = ["onPageEvent", "onTabClose"]; functions.forEach(function(name) { // Event disambiguation is handled by name munging. See // chrome/browser/extensions/extension_devtools_events.h for the C++ // equivalent of this logic. tabIdProxy[name] = new chrome.Event("devtools." + tabId + "." + name); }); return tabIdProxy; }; var canvas; function setIconCommon(details, name, parameters, actionType, iconSize, nativeFunction) { if ("iconIndex" in details) { sendRequest(name, [details], parameters); } else if ("imageData" in details) { // Verify that this at least looks like an ImageData element. // Unfortunately, we cannot use instanceof because the ImageData // constructor is not public. // // We do this manually instead of using JSONSchema to avoid having these // properties show up in the doc. if (!("width" in details.imageData) || !("height" in details.imageData) || !("data" in details.imageData)) { throw new Error( "The imageData property must contain an ImageData object."); } if (details.imageData.width > iconSize || details.imageData.height > iconSize) { throw new Error( "The imageData property must contain an ImageData object that " + "is no larger than " + iconSize + " pixels square."); } sendRequest(name, [details], parameters, {noStringify: true, nativeFunction: nativeFunction}); } else if ("path" in details) { var img = new Image(); img.onerror = function() { console.error("Could not load " + actionType + " icon '" + details.path + "'."); }; img.onload = function() { var canvas = document.createElement("canvas"); canvas.width = img.width > iconSize ? iconSize : img.width; canvas.height = img.height > iconSize ? iconSize : img.height; var canvas_context = canvas.getContext('2d'); canvas_context.clearRect(0, 0, canvas.width, canvas.height); canvas_context.drawImage(img, 0, 0, canvas.width, canvas.height); delete details.path; details.imageData = canvas_context.getImageData(0, 0, canvas.width, canvas.height); sendRequest(name, [details], parameters, {noStringify: true, nativeFunction: nativeFunction}); }; img.src = details.path; } else { throw new Error( "Either the path or imageData property must be specified."); } } function setExtensionActionIconCommon(details, name, parameters, actionType) { var EXTENSION_ACTION_ICON_SIZE = 19; setIconCommon(details, name, parameters, actionType, EXTENSION_ACTION_ICON_SIZE, SetIconCommon); } apiFunctions["browserAction.setIcon"].handleRequest = function(details) { setExtensionActionIconCommon( details, this.name, this.definition.parameters, "browser action"); }; apiFunctions["pageAction.setIcon"].handleRequest = function(details) { setExtensionActionIconCommon( details, this.name, this.definition.parameters, "page action"); }; apiFunctions["experimental.sidebar.setIcon"].handleRequest = function(details) { var SIDEBAR_ICON_SIZE = 16; setIconCommon( details, this.name, this.definition.parameters, "sidebar", SIDEBAR_ICON_SIZE, SetIconCommon); }; apiFunctions["contextMenus.create"].handleRequest = function() { var args = arguments; var id = GetNextContextMenuId(); args[0].generatedId = id; sendRequest(this.name, args, this.definition.parameters, {customCallback: this.customCallback}); return id; }; apiFunctions["omnibox.setDefaultSuggestion"].handleRequest = function(details) { var parseResult = parseOmniboxDescription(details.description); sendRequest(this.name, [parseResult], this.definition.parameters); }; apiFunctions["experimental.webRequest.addEventListener"].handleRequest = function() { var args = Array.prototype.slice.call(arguments); sendRequest(this.name, args, this.definition.parameters, {forIOThread: true}); }; apiFunctions["experimental.webRequest.eventHandled"].handleRequest = function() { var args = Array.prototype.slice.call(arguments); sendRequest(this.name, args, this.definition.parameters, {forIOThread: true}); }; apiFunctions["contextMenus.create"].customCallback = function(name, request, response) { if (chrome.extension.lastError) { return; } var id = request.args[0].generatedId; // Set up the onclick handler if we were passed one in the request. var onclick = request.args.length ? request.args[0].onclick : null; if (onclick) { chromeHidden.contextMenus.ensureListenerSetup(); chromeHidden.contextMenus.handlers[id] = onclick; } }; apiFunctions["contextMenus.remove"].customCallback = function(name, request, response) { if (chrome.extension.lastError) { return; } var id = request.args[0]; delete chromeHidden.contextMenus.handlers[id]; }; apiFunctions["contextMenus.update"].customCallback = function(name, request, response) { if (chrome.extension.lastError) { return; } var id = request.args[0]; if (request.args[1].onclick) { chromeHidden.contextMenus.handlers[id] = request.args[1].onclick; } }; apiFunctions["contextMenus.removeAll"].customCallback = function(name, request, response) { if (chrome.extension.lastError) { return; } chromeHidden.contextMenus.handlers = {}; }; apiFunctions["tabs.captureVisibleTab"].updateArgumentsPreValidate = function() { // Old signature: // captureVisibleTab(int windowId, function callback); // New signature: // captureVisibleTab(int windowId, object details, function callback); // // TODO(skerner): The next step to omitting optional arguments is the // replacement of this code with code that matches arguments by type. // Once this is working for captureVisibleTab() it can be enabled for // the rest of the API. See crbug/29215 . if (arguments.length == 2 && typeof(arguments[1]) == "function") { // If the old signature is used, add a null details object. newArgs = [arguments[0], null, arguments[1]]; } else { newArgs = arguments; } return newArgs; }; apiFunctions["omnibox.sendSuggestions"].updateArgumentsPostValidate = function(requestId, userSuggestions) { var suggestions = []; for (var i = 0; i < userSuggestions.length; i++) { var parseResult = parseOmniboxDescription( userSuggestions[i].description); parseResult.content = userSuggestions[i].content; suggestions.push(parseResult); } return [requestId, suggestions]; }; apiFunctions["tts.speak"].handleRequest = function() { var args = arguments; if (args.length > 1 && args[1] && args[1].onEvent) { var id = GetNextTtsEventId(); args[1].srcId = id; chromeHidden.tts.handlers[id] = args[1].onEvent; } sendRequest(this.name, args, this.definition.parameters); return id; }; if (chrome.test) { chrome.test.getApiDefinitions = GetExtensionAPIDefinition; } setupPageActionEvents(extensionId); setupToolstripEvents(GetRenderViewId()); setupHiddenContextMenuEvent(extensionId); setupInputEvents(); setupOmniboxEvents(); setupTtsEvents(); }); if (!chrome.experimental) chrome.experimental = {}; if (!chrome.experimental.accessibility) chrome.experimental.accessibility = {}; if (!chrome.tts) chrome.tts = {}; if (!chrome.ttsEngine) chrome.ttsEngine = {}; if (!chrome.experimental.downloads) chrome.experimental.downloads = {}; })();