Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(176)

Side by Side Diff: chrome/renderer/resources/extensions/extension_process_bindings.js

Issue 8540012: Enable extension APIs for content scripts. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // This script contains privileged chrome extension related javascript APIs.
6 // It is loaded by pages whose URL has the chrome-extension protocol.
7
8 var chrome = chrome || {};
9 (function() {
10 native function GetExtensionAPIDefinition();
11 native function StartRequest();
12 native function GetChromeHidden();
13 native function GetNextRequestId();
14 native function Print();
15
16 native function GetCurrentPageActions(extensionId);
17 native function GetExtensionViews();
18 native function GetNextContextMenuId();
19 native function GetNextTtsEventId();
20 native function OpenChannelToTab();
21 native function GetRenderViewId();
22 native function SetIconCommon();
23 native function GetUniqueSubEventName(eventName);
24 native function GetLocalFileSystem(name, path);
25 native function DecodeJPEG(jpegImage);
26 native function CreateBlob(filePath);
27
28 var chromeHidden = GetChromeHidden();
29
30 if (!chrome)
31 chrome = {};
32
33 function forEach(dict, f) {
34 for (key in dict) {
35 if (dict.hasOwnProperty(key))
36 f(key, dict[key]);
37 }
38 }
39
40 // Validate arguments.
41 chromeHidden.validationTypes = [];
42 chromeHidden.validate = function(args, schemas) {
43 if (args.length > schemas.length)
44 throw new Error("Too many arguments.");
45
46 for (var i = 0; i < schemas.length; i++) {
47 if (i in args && args[i] !== null && args[i] !== undefined) {
48 var validator = new chromeHidden.JSONSchemaValidator();
49 validator.addTypes(chromeHidden.validationTypes);
50 validator.validate(args[i], schemas[i]);
51 if (validator.errors.length == 0)
52 continue;
53
54 var message = "Invalid value for argument " + (i + 1) + ". ";
55 for (var i = 0, err; err = validator.errors[i]; i++) {
56 if (err.path) {
57 message += "Property '" + err.path + "': ";
58 }
59 message += err.message;
60 message = message.substring(0, message.length - 1);
61 message += ", ";
62 }
63 message = message.substring(0, message.length - 2);
64 message += ".";
65
66 throw new Error(message);
67 } else if (!schemas[i].optional) {
68 throw new Error("Parameter " + (i + 1) + " is required.");
69 }
70 }
71 };
72
73 // Callback handling.
74 var requests = [];
75 chromeHidden.handleResponse = function(requestId, name,
76 success, response, error) {
77 try {
78 var request = requests[requestId];
79 if (success) {
80 delete chrome.extension.lastError;
81 } else {
82 if (!error) {
83 error = "Unknown error.";
84 }
85 console.error("Error during " + name + ": " + error);
86 chrome.extension.lastError = {
87 "message": error
88 };
89 }
90
91 if (request.customCallback) {
92 request.customCallback(name, request, response);
93 }
94
95 if (request.callback) {
96 // Callbacks currently only support one callback argument.
97 var callbackArgs = response ? [chromeHidden.JSON.parse(response)] : [];
98
99 // Validate callback in debug only -- and only when the
100 // caller has provided a callback. Implementations of api
101 // calls my not return data if they observe the caller
102 // has not provided a callback.
103 if (chromeHidden.validateCallbacks && !error) {
104 try {
105 if (!request.callbackSchema.parameters) {
106 throw "No callback schemas defined";
107 }
108
109 if (request.callbackSchema.parameters.length > 1) {
110 throw "Callbacks may only define one parameter";
111 }
112
113 chromeHidden.validate(callbackArgs,
114 request.callbackSchema.parameters);
115 } catch (exception) {
116 return "Callback validation error during " + name + " -- " +
117 exception.stack;
118 }
119 }
120
121 if (response) {
122 request.callback(callbackArgs[0]);
123 } else {
124 request.callback();
125 }
126 }
127 } finally {
128 delete requests[requestId];
129 delete chrome.extension.lastError;
130 }
131
132 return undefined;
133 };
134
135 function prepareRequest(args, argSchemas) {
136 var request = {};
137 var argCount = args.length;
138
139 // Look for callback param.
140 if (argSchemas.length > 0 &&
141 args.length == argSchemas.length &&
142 argSchemas[argSchemas.length - 1].type == "function") {
143 request.callback = args[argSchemas.length - 1];
144 request.callbackSchema = argSchemas[argSchemas.length - 1];
145 --argCount;
146 }
147
148 request.args = [];
149 for (var k = 0; k < argCount; k++) {
150 request.args[k] = args[k];
151 }
152
153 return request;
154 }
155
156 // Send an API request and optionally register a callback.
157 // |opt_args| is an object with optional parameters as follows:
158 // - noStringify: true if we should not stringify the request arguments.
159 // - customCallback: a callback that should be called instead of the standard
160 // callback.
161 // - nativeFunction: the v8 native function to handle the request, or
162 // StartRequest if missing.
163 // - forIOThread: true if this function should be handled on the browser IO
164 // thread.
165 function sendRequest(functionName, args, argSchemas, opt_args) {
166 if (!opt_args)
167 opt_args = {};
168 var request = prepareRequest(args, argSchemas);
169 if (opt_args.customCallback) {
170 request.customCallback = opt_args.customCallback;
171 }
172 // JSON.stringify doesn't support a root object which is undefined.
173 if (request.args === undefined)
174 request.args = null;
175
176 var sargs = opt_args.noStringify ?
177 request.args : chromeHidden.JSON.stringify(request.args);
178 var nativeFunction = opt_args.nativeFunction || StartRequest;
179
180 var requestId = GetNextRequestId();
181 requests[requestId] = request;
182 var hasCallback =
183 (request.callback || opt_args.customCallback) ? true : false;
184 return nativeFunction(functionName, sargs, requestId, hasCallback,
185 opt_args.forIOThread);
186 }
187
188 // --- Setup additional api's not currently handled in common/extensions/api
189
190 // WebRequestEvent object. This is used for special webRequest events with
191 // extra parameters. Each invocation of addListener creates a new named
192 // sub-event. That sub-event is associated with the extra parameters in the
193 // browser process, so that only it is dispatched when the main event occurs
194 // matching the extra parameters.
195 //
196 // Example:
197 // chrome.webRequest.onBeforeRequest.addListener(
198 // callback, {urls: "http://*.google.com/*"});
199 // ^ callback will only be called for onBeforeRequests matching the filter.
200 chrome.WebRequestEvent =
201 function(eventName, opt_argSchemas, opt_extraArgSchemas) {
202 if (typeof eventName != "string")
203 throw new Error("chrome.WebRequestEvent requires an event name.");
204
205 this.eventName_ = eventName;
206 this.argSchemas_ = opt_argSchemas;
207 this.extraArgSchemas_ = opt_extraArgSchemas;
208 this.subEvents_ = [];
209 };
210
211 // Test if the given callback is registered for this event.
212 chrome.WebRequestEvent.prototype.hasListener = function(cb) {
213 return this.findListener_(cb) > -1;
214 };
215
216 // Test if any callbacks are registered fur thus event.
217 chrome.WebRequestEvent.prototype.hasListeners = function(cb) {
218 return this.subEvents_.length > 0;
219 };
220
221 // Registers a callback to be called when this event is dispatched. If
222 // opt_filter is specified, then the callback is only called for events that
223 // match the given filters. If opt_extraInfo is specified, the given optional
224 // info is sent to the callback.
225 chrome.WebRequestEvent.prototype.addListener =
226 function(cb, opt_filter, opt_extraInfo) {
227 var subEventName = GetUniqueSubEventName(this.eventName_);
228 // Note: this could fail to validate, in which case we would not add the
229 // subEvent listener.
230 chromeHidden.validate(Array.prototype.slice.call(arguments, 1),
231 this.extraArgSchemas_);
232 chrome.experimental.webRequest.addEventListener(
233 cb, opt_filter, opt_extraInfo, this.eventName_, subEventName);
234
235 var subEvent = new chrome.Event(subEventName, this.argSchemas_);
236 var subEventCallback = cb;
237 if (opt_extraInfo && opt_extraInfo.indexOf("blocking") >= 0) {
238 var eventName = this.eventName_;
239 subEventCallback = function() {
240 var requestId = arguments[0].requestId;
241 try {
242 var result = cb.apply(null, arguments);
243 chrome.experimental.webRequest.eventHandled(
244 eventName, subEventName, requestId, result);
245 } catch (e) {
246 chrome.experimental.webRequest.eventHandled(
247 eventName, subEventName, requestId);
248 throw e;
249 }
250 };
251 } else if (opt_extraInfo && opt_extraInfo.indexOf("asyncBlocking") >= 0) {
252 var eventName = this.eventName_;
253 subEventCallback = function() {
254 var details = arguments[0];
255 var requestId = details.requestId;
256 var handledCallback = function(response) {
257 chrome.experimental.webRequest.eventHandled(
258 eventName, subEventName, requestId, response);
259 };
260 cb.apply(null, [details, handledCallback]);
261 };
262 }
263 this.subEvents_.push(
264 {subEvent: subEvent, callback: cb, subEventCallback: subEventCallback});
265 subEvent.addListener(subEventCallback);
266 };
267
268 // Unregisters a callback.
269 chrome.WebRequestEvent.prototype.removeListener = function(cb) {
270 var idx = this.findListener_(cb);
271 if (idx < 0) {
272 return;
273 }
274
275 var e = this.subEvents_[idx];
276 e.subEvent.removeListener(e.subEventCallback);
277 if (e.subEvent.hasListeners()) {
278 console.error(
279 "Internal error: webRequest subEvent has orphaned listeners.");
280 }
281 this.subEvents_.splice(idx, 1);
282 };
283
284 chrome.WebRequestEvent.prototype.findListener_ = function(cb) {
285 for (var i in this.subEvents_) {
286 var e = this.subEvents_[i];
287 if (e.callback === cb) {
288 if (e.subEvent.findListener_(e.subEventCallback) > -1)
289 return i;
290 console.error("Internal error: webRequest subEvent has no callback.");
291 }
292 }
293
294 return -1;
295 };
296
297 function CustomBindingsObject() {
298 }
299 CustomBindingsObject.prototype.setSchema = function(schema) {
300 // The functions in the schema are in list form, so we move them into a
301 // dictionary for easier access.
302 var self = this;
303 self.parameters = {};
304 schema.functions.forEach(function(f) {
305 self.parameters[f.name] = f.parameters;
306 });
307 };
308
309 function extendSchema(schema) {
310 var extendedSchema = schema.slice();
311 extendedSchema.unshift({'type': 'string'});
312 return extendedSchema;
313 }
314
315 var customBindings = {};
316
317 function setupChromeSetting() {
318 function ChromeSetting(prefKey, valueSchema) {
319 this.get = function(details, callback) {
320 var getSchema = this.parameters.get;
321 chromeHidden.validate([details, callback], getSchema);
322 return sendRequest('types.ChromeSetting.get',
323 [prefKey, details, callback],
324 extendSchema(getSchema));
325 };
326 this.set = function(details, callback) {
327 var setSchema = this.parameters.set.slice();
328 setSchema[0].properties.value = valueSchema;
329 chromeHidden.validate([details, callback], setSchema);
330 return sendRequest('types.ChromeSetting.set',
331 [prefKey, details, callback],
332 extendSchema(setSchema));
333 };
334 this.clear = function(details, callback) {
335 var clearSchema = this.parameters.clear;
336 chromeHidden.validate([details, callback], clearSchema);
337 return sendRequest('types.ChromeSetting.clear',
338 [prefKey, details, callback],
339 extendSchema(clearSchema));
340 };
341 this.onChange = new chrome.Event('types.ChromeSetting.' + prefKey +
342 '.onChange');
343 };
344 ChromeSetting.prototype = new CustomBindingsObject();
345 customBindings['ChromeSetting'] = ChromeSetting;
346 }
347
348 function setupContentSetting() {
349 function ContentSetting(contentType, settingSchema) {
350 this.get = function(details, callback) {
351 var getSchema = this.parameters.get;
352 chromeHidden.validate([details, callback], getSchema);
353 return sendRequest('contentSettings.get',
354 [contentType, details, callback],
355 extendSchema(getSchema));
356 };
357 this.set = function(details, callback) {
358 var setSchema = this.parameters.set.slice();
359 setSchema[0].properties.setting = settingSchema;
360 chromeHidden.validate([details, callback], setSchema);
361 return sendRequest('contentSettings.set',
362 [contentType, details, callback],
363 extendSchema(setSchema));
364 };
365 this.clear = function(details, callback) {
366 var clearSchema = this.parameters.clear;
367 chromeHidden.validate([details, callback], clearSchema);
368 return sendRequest('contentSettings.clear',
369 [contentType, details, callback],
370 extendSchema(clearSchema));
371 };
372 this.getResourceIdentifiers = function(callback) {
373 var schema = this.parameters.getResourceIdentifiers;
374 chromeHidden.validate([callback], schema);
375 return sendRequest(
376 'contentSettings.getResourceIdentifiers',
377 [contentType, callback],
378 extendSchema(schema));
379 };
380 }
381 ContentSetting.prototype = new CustomBindingsObject();
382 customBindings['ContentSetting'] = ContentSetting;
383 }
384
385 function setupInputEvents() {
386 chrome.experimental.input.onKeyEvent.dispatch =
387 function(engineID, keyData) {
388 var args = Array.prototype.slice.call(arguments);
389 if (this.validate_) {
390 var validationErrors = this.validate_(args);
391 if (validationErrors) {
392 chrome.experimental.input.eventHandled(requestId, false);
393 return validationErrors;
394 }
395 }
396 if (this.listeners_.length > 1) {
397 console.error("Too many listeners for 'onKeyEvent': " + e.stack);
398 chrome.experimental.input.eventHandled(requestId, false);
399 return;
400 }
401 for (var i = 0; i < this.listeners_.length; i++) {
402 try {
403 var requestId = keyData.requestId;
404 var result = this.listeners_[i].apply(null, args);
405 chrome.experimental.input.eventHandled(requestId, result);
406 } catch (e) {
407 console.error("Error in event handler for 'onKeyEvent': " + e.stack);
408 chrome.experimental.input.eventHandled(requestId, false);
409 }
410 }
411 };
412 }
413
414 // Page action events send (pageActionId, {tabId, tabUrl}).
415 function setupPageActionEvents(extensionId) {
416 var pageActions = GetCurrentPageActions(extensionId);
417
418 var oldStyleEventName = "pageActions";
419 // TODO(EXTENSIONS_DEPRECATED): only one page action
420 for (var i = 0; i < pageActions.length; ++i) {
421 // Setup events for each extension_id/page_action_id string we find.
422 chrome.pageActions[pageActions[i]] = new chrome.Event(oldStyleEventName);
423 }
424 }
425
426 function setupHiddenContextMenuEvent(extensionId) {
427 chromeHidden.contextMenus = {};
428 chromeHidden.contextMenus.handlers = {};
429 var eventName = "contextMenus";
430 chromeHidden.contextMenus.event = new chrome.Event(eventName);
431 chromeHidden.contextMenus.ensureListenerSetup = function() {
432 if (chromeHidden.contextMenus.listening) {
433 return;
434 }
435 chromeHidden.contextMenus.listening = true;
436 chromeHidden.contextMenus.event.addListener(function() {
437 // An extension context menu item has been clicked on - fire the onclick
438 // if there is one.
439 var id = arguments[0].menuItemId;
440 var onclick = chromeHidden.contextMenus.handlers[id];
441 if (onclick) {
442 onclick.apply(null, arguments);
443 }
444 });
445 };
446 }
447
448 // Remove invalid characters from |text| so that it is suitable to use
449 // for |AutocompleteMatch::contents|.
450 function sanitizeString(text) {
451 // NOTE: This logic mirrors |AutocompleteMatch::SanitizeString()|.
452 // 0x2028 = line separator; 0x2029 = paragraph separator.
453 var kRemoveChars = /(\r|\n|\t|\u2028|\u2029)/gm;
454 return text.trimLeft().replace(kRemoveChars, '');
455 }
456
457 // Parses the xml syntax supported by omnibox suggestion results. Returns an
458 // object with two properties: 'description', which is just the text content,
459 // and 'descriptionStyles', which is an array of style objects in a format
460 // understood by the C++ backend.
461 function parseOmniboxDescription(input) {
462 var domParser = new DOMParser();
463
464 // The XML parser requires a single top-level element, but we want to
465 // support things like 'hello, <match>world</match>!'. So we wrap the
466 // provided text in generated root level element.
467 var root = domParser.parseFromString(
468 '<fragment>' + input + '</fragment>', 'text/xml');
469
470 // DOMParser has a terrible error reporting facility. Errors come out nested
471 // inside the returned document.
472 var error = root.querySelector('parsererror div');
473 if (error) {
474 throw new Error(error.textContent);
475 }
476
477 // Otherwise, it's valid, so build up the result.
478 var result = {
479 description: '',
480 descriptionStyles: []
481 };
482
483 // Recursively walk the tree.
484 (function(node) {
485 for (var i = 0, child; child = node.childNodes[i]; i++) {
486 // Append text nodes to our description.
487 if (child.nodeType == Node.TEXT_NODE) {
488 result.description += sanitizeString(child.nodeValue);
489 continue;
490 }
491
492 // Process and descend into a subset of recognized tags.
493 if (child.nodeType == Node.ELEMENT_NODE &&
494 (child.nodeName == 'dim' || child.nodeName == 'match' ||
495 child.nodeName == 'url')) {
496 var style = {
497 'type': child.nodeName,
498 'offset': result.description.length
499 };
500 result.descriptionStyles.push(style);
501 arguments.callee(child);
502 style.length = result.description.length - style.offset;
503 continue;
504 }
505
506 // Descend into all other nodes, even if they are unrecognized, for
507 // forward compat.
508 arguments.callee(child);
509 }
510 })(root);
511
512 return result;
513 }
514
515 function setupOmniboxEvents() {
516 chrome.omnibox.onInputChanged.dispatch =
517 function(text, requestId) {
518 var suggestCallback = function(suggestions) {
519 chrome.omnibox.sendSuggestions(requestId, suggestions);
520 };
521 chrome.Event.prototype.dispatch.apply(this, [text, suggestCallback]);
522 };
523 }
524
525 function setupTtsEvents() {
526 chromeHidden.tts = {};
527 chromeHidden.tts.handlers = {};
528 chrome.ttsEngine.onSpeak.dispatch =
529 function(text, options, requestId) {
530 var sendTtsEvent = function(event) {
531 chrome.ttsEngine.sendTtsEvent(requestId, event);
532 };
533 chrome.Event.prototype.dispatch.apply(
534 this, [text, options, sendTtsEvent]);
535 };
536 try {
537 chrome.tts.onEvent.addListener(
538 function(event) {
539 var eventHandler = chromeHidden.tts.handlers[event.srcId];
540 if (eventHandler) {
541 eventHandler({
542 type: event.type,
543 charIndex: event.charIndex,
544 errorMessage: event.errorMessage
545 });
546 if (event.isFinalEvent) {
547 delete chromeHidden.tts.handlers[event.srcId];
548 }
549 }
550 });
551 } catch (e) {
552 // This extension doesn't have permission to access TTS, so we
553 // can safely ignore this.
554 }
555 }
556
557 // Get the platform from navigator.appVersion.
558 function getPlatform() {
559 var platforms = [
560 [/CrOS Touch/, "chromeos touch"],
561 [/CrOS/, "chromeos"],
562 [/Linux/, "linux"],
563 [/Mac/, "mac"],
564 [/Win/, "win"],
565 ];
566
567 for (var i = 0; i < platforms.length; i++) {
568 if (platforms[i][0].test(navigator.appVersion)) {
569 return platforms[i][1];
570 }
571 }
572 return "unknown";
573 }
574
575 chromeHidden.onLoad.addListener(function(extensionId, isExtensionProcess,
576 isIncognitoProcess) {
577 if (!isExtensionProcess)
578 return;
579
580 // Setup the ChromeSetting class so we can use it to construct
581 // ChromeSetting objects from the API definition.
582 setupChromeSetting();
583
584 // Setup the ContentSetting class so we can use it to construct
585 // ContentSetting objects from the API definition.
586 setupContentSetting();
587
588 // |apiFunctions| is a hash of name -> object that stores the
589 // name & definition of the apiFunction. Custom handling of api functions
590 // is implemented by adding a "handleRequest" function to the object.
591 var apiFunctions = {};
592
593 // Read api definitions and setup api functions in the chrome namespace.
594 // TODO(rafaelw): Consider defining a json schema for an api definition
595 // and validating either here, in a unit_test or both.
596 // TODO(rafaelw): Handle synchronous functions.
597 // TOOD(rafaelw): Consider providing some convenient override points
598 // for api functions that wish to insert themselves into the call.
599 var apiDefinitions = chromeHidden.JSON.parse(GetExtensionAPIDefinition());
600 var platform = getPlatform();
601
602 apiDefinitions.forEach(function(apiDef) {
603 // Check platform, if apiDef has platforms key.
604 if (apiDef.platforms && apiDef.platforms.indexOf(platform) == -1) {
605 return;
606 }
607
608 var module = chrome;
609 var namespaces = apiDef.namespace.split('.');
610 for (var index = 0, name; name = namespaces[index]; index++) {
611 module[name] = module[name] || {};
612 module = module[name];
613 }
614
615 // Add types to global validationTypes
616 if (apiDef.types) {
617 apiDef.types.forEach(function(t) {
618 chromeHidden.validationTypes.push(t);
619 if (t.type == 'object' && customBindings[t.id]) {
620 customBindings[t.id].prototype.setSchema(t);
621 }
622 });
623 }
624
625 // Setup Functions.
626 if (apiDef.functions) {
627 apiDef.functions.forEach(function(functionDef) {
628 // Module functions may have been defined earlier by hand. Don't
629 // clobber them.
630 if (module[functionDef.name])
631 return;
632
633 var apiFunction = {};
634 apiFunction.definition = functionDef;
635 apiFunction.name = apiDef.namespace + "." + functionDef.name;
636 apiFunctions[apiFunction.name] = apiFunction;
637
638 module[functionDef.name] = (function() {
639 var args = arguments;
640 if (this.updateArgumentsPreValidate)
641 args = this.updateArgumentsPreValidate.apply(this, args);
642 chromeHidden.validate(args, this.definition.parameters);
643 if (this.updateArgumentsPostValidate)
644 args = this.updateArgumentsPostValidate.apply(this, args);
645
646 var retval;
647 if (this.handleRequest) {
648 retval = this.handleRequest.apply(this, args);
649 } else {
650 retval = sendRequest(this.name, args,
651 this.definition.parameters,
652 {customCallback: this.customCallback});
653 }
654
655 // Validate return value if defined - only in debug.
656 if (chromeHidden.validateCallbacks &&
657 chromeHidden.validate &&
658 this.definition.returns) {
659 chromeHidden.validate([retval], [this.definition.returns]);
660 }
661 return retval;
662 }).bind(apiFunction);
663 });
664 }
665
666 // Setup Events
667 if (apiDef.events) {
668 apiDef.events.forEach(function(eventDef) {
669 // Module events may have been defined earlier by hand. Don't clobber
670 // them.
671 if (module[eventDef.name])
672 return;
673
674 var eventName = apiDef.namespace + "." + eventDef.name;
675 if (apiDef.namespace == "experimental.webRequest") {
676 module[eventDef.name] = new chrome.WebRequestEvent(eventName,
677 eventDef.parameters, eventDef.extraParameters);
678 } else {
679 module[eventDef.name] = new chrome.Event(eventName,
680 eventDef.parameters);
681 }
682 });
683 }
684
685 function addProperties(m, def) {
686 // Parse any values defined for properties.
687 if (def.properties) {
688 forEach(def.properties, function(prop, property) {
689 var value = property.value;
690 if (value) {
691 if (property.type === 'integer') {
692 value = parseInt(value);
693 } else if (property.type === 'boolean') {
694 value = value === "true";
695 } else if (property["$ref"]) {
696 var constructor = customBindings[property["$ref"]];
697 var args = value;
698 // For an object property, |value| is an array of constructor
699 // arguments, but we want to pass the arguments directly
700 // (i.e. not as an array), so we have to fake calling |new| on
701 // the constructor.
702 value = { __proto__: constructor.prototype };
703 constructor.apply(value, args);
704 } else if (property.type === 'object') {
705 // Recursively add properties.
706 addProperties(value, property);
707 } else if (property.type !== 'string') {
708 throw "NOT IMPLEMENTED (extension_api.json error): Cannot " +
709 "parse values for type \"" + property.type + "\"";
710 }
711 }
712 if (value) {
713 m[prop] = value;
714 }
715 });
716 }
717 }
718
719 addProperties(module, apiDef);
720 });
721
722 // getTabContentses is retained for backwards compatibility
723 // See http://crbug.com/21433
724 chrome.extension.getTabContentses = chrome.extension.getExtensionTabs;
725 // TOOD(mihaip): remove this alias once the webstore stops calling
726 // beginInstallWithManifest2.
727 // See http://crbug.com/100242
728 chrome.webstorePrivate.beginInstallWithManifest2 =
729 chrome.webstorePrivate.beginInstallWithManifest3;
730
731 apiFunctions["tabs.connect"].handleRequest = function(tabId, connectInfo) {
732 var name = "";
733 if (connectInfo) {
734 name = connectInfo.name || name;
735 }
736 var portId = OpenChannelToTab(tabId, chromeHidden.extensionId, name);
737 return chromeHidden.Port.createPort(portId, name);
738 };
739
740 apiFunctions["tabs.sendRequest"].handleRequest =
741 function(tabId, request, responseCallback) {
742 var port = chrome.tabs.connect(tabId,
743 {name: chromeHidden.kRequestChannel});
744 port.postMessage(request);
745 port.onDisconnect.addListener(function() {
746 // For onDisconnects, we only notify the callback if there was an error.
747 if (chrome.extension.lastError && responseCallback)
748 responseCallback();
749 });
750 port.onMessage.addListener(function(response) {
751 try {
752 if (responseCallback)
753 responseCallback(response);
754 } finally {
755 port.disconnect();
756 port = null;
757 }
758 });
759 };
760
761 apiFunctions["experimental.savePage.saveAsMHTML"].customCallback =
762 function(name, request, response) {
763 var params = chromeHidden.JSON.parse(response);
764 var path = params.mhtmlFilePath;
765 var size = params.mhtmlFileLength;
766
767 if (request.callback)
768 request.callback(CreateBlob(path, size));
769
770 request.callback = null;
771 };
772
773 apiFunctions["fileBrowserPrivate.requestLocalFileSystem"].customCallback =
774 function(name, request, response) {
775 var resp = response ? [chromeHidden.JSON.parse(response)] : [];
776 var fs = null;
777 if (!resp[0].error)
778 fs = GetLocalFileSystem(resp[0].name, resp[0].path);
779 if (request.callback)
780 request.callback(fs);
781 request.callback = null;
782 };
783
784 apiFunctions["chromePrivate.decodeJPEG"].handleRequest =
785 function(jpeg_image) {
786 return DecodeJPEG(jpeg_image);
787 };
788
789 apiFunctions["extension.getViews"].handleRequest = function(properties) {
790 var windowId = -1;
791 var type = "ALL";
792 if (typeof(properties) != "undefined") {
793 if (typeof(properties.type) != "undefined") {
794 type = properties.type;
795 }
796 if (typeof(properties.windowId) != "undefined") {
797 windowId = properties.windowId;
798 }
799 }
800 return GetExtensionViews(windowId, type) || null;
801 };
802
803 apiFunctions["extension.getBackgroundPage"].handleRequest = function() {
804 return GetExtensionViews(-1, "BACKGROUND")[0] || null;
805 };
806
807 apiFunctions["extension.getExtensionTabs"].handleRequest =
808 function(windowId) {
809 if (typeof(windowId) == "undefined")
810 windowId = -1;
811 return GetExtensionViews(windowId, "TAB");
812 };
813
814 apiFunctions["devtools.getTabEvents"].handleRequest = function(tabId) {
815 var tabIdProxy = {};
816 var functions = ["onPageEvent", "onTabClose"];
817 functions.forEach(function(name) {
818 // Event disambiguation is handled by name munging. See
819 // chrome/browser/extensions/extension_devtools_events.h for the C++
820 // equivalent of this logic.
821 tabIdProxy[name] = new chrome.Event("devtools." + tabId + "." + name);
822 });
823 return tabIdProxy;
824 };
825
826 var canvas;
827 function setIconCommon(details, name, parameters, actionType, iconSize,
828 nativeFunction) {
829 if ("iconIndex" in details) {
830 sendRequest(name, [details], parameters);
831 } else if ("imageData" in details) {
832 // Verify that this at least looks like an ImageData element.
833 // Unfortunately, we cannot use instanceof because the ImageData
834 // constructor is not public.
835 //
836 // We do this manually instead of using JSONSchema to avoid having these
837 // properties show up in the doc.
838 if (!("width" in details.imageData) ||
839 !("height" in details.imageData) ||
840 !("data" in details.imageData)) {
841 throw new Error(
842 "The imageData property must contain an ImageData object.");
843 }
844
845 if (details.imageData.width > iconSize ||
846 details.imageData.height > iconSize) {
847 throw new Error(
848 "The imageData property must contain an ImageData object that " +
849 "is no larger than " + iconSize + " pixels square.");
850 }
851
852 sendRequest(name, [details], parameters,
853 {noStringify: true, nativeFunction: nativeFunction});
854 } else if ("path" in details) {
855 var img = new Image();
856 img.onerror = function() {
857 console.error("Could not load " + actionType + " icon '" +
858 details.path + "'.");
859 };
860 img.onload = function() {
861 var canvas = document.createElement("canvas");
862 canvas.width = img.width > iconSize ? iconSize : img.width;
863 canvas.height = img.height > iconSize ? iconSize : img.height;
864
865 var canvas_context = canvas.getContext('2d');
866 canvas_context.clearRect(0, 0, canvas.width, canvas.height);
867 canvas_context.drawImage(img, 0, 0, canvas.width, canvas.height);
868 delete details.path;
869 details.imageData = canvas_context.getImageData(0, 0, canvas.width,
870 canvas.height);
871 sendRequest(name, [details], parameters,
872 {noStringify: true, nativeFunction: nativeFunction});
873 };
874 img.src = details.path;
875 } else {
876 throw new Error(
877 "Either the path or imageData property must be specified.");
878 }
879 }
880
881 function setExtensionActionIconCommon(details, name, parameters,
882 actionType) {
883 var EXTENSION_ACTION_ICON_SIZE = 19;
884 setIconCommon(details, name, parameters, actionType,
885 EXTENSION_ACTION_ICON_SIZE, SetIconCommon);
886 }
887
888 apiFunctions["browserAction.setIcon"].handleRequest = function(details) {
889 setExtensionActionIconCommon(
890 details, this.name, this.definition.parameters, "browser action");
891 };
892
893 apiFunctions["pageAction.setIcon"].handleRequest = function(details) {
894 setExtensionActionIconCommon(
895 details, this.name, this.definition.parameters, "page action");
896 };
897
898 apiFunctions["experimental.sidebar.setIcon"].handleRequest =
899 function(details) {
900 var SIDEBAR_ICON_SIZE = 16;
901 setIconCommon(
902 details, this.name, this.definition.parameters, "sidebar",
903 SIDEBAR_ICON_SIZE, SetIconCommon);
904 };
905
906 apiFunctions["contextMenus.create"].handleRequest =
907 function() {
908 var args = arguments;
909 var id = GetNextContextMenuId();
910 args[0].generatedId = id;
911 sendRequest(this.name, args, this.definition.parameters,
912 {customCallback: this.customCallback});
913 return id;
914 };
915
916 apiFunctions["omnibox.setDefaultSuggestion"].handleRequest =
917 function(details) {
918 var parseResult = parseOmniboxDescription(details.description);
919 sendRequest(this.name, [parseResult], this.definition.parameters);
920 };
921
922 apiFunctions["experimental.webRequest.addEventListener"].handleRequest =
923 function() {
924 var args = Array.prototype.slice.call(arguments);
925 sendRequest(this.name, args, this.definition.parameters,
926 {forIOThread: true});
927 };
928
929 apiFunctions["experimental.webRequest.eventHandled"].handleRequest =
930 function() {
931 var args = Array.prototype.slice.call(arguments);
932 sendRequest(this.name, args, this.definition.parameters,
933 {forIOThread: true});
934 };
935
936 apiFunctions["experimental.webRequest.handlerBehaviorChanged"].
937 handleRequest = function() {
938 var args = Array.prototype.slice.call(arguments);
939 sendRequest(this.name, args, this.definition.parameters,
940 {forIOThread: true});
941 };
942
943 apiFunctions["contextMenus.create"].customCallback =
944 function(name, request, response) {
945 if (chrome.extension.lastError) {
946 return;
947 }
948
949 var id = request.args[0].generatedId;
950
951 // Set up the onclick handler if we were passed one in the request.
952 var onclick = request.args.length ? request.args[0].onclick : null;
953 if (onclick) {
954 chromeHidden.contextMenus.ensureListenerSetup();
955 chromeHidden.contextMenus.handlers[id] = onclick;
956 }
957 };
958
959 apiFunctions["contextMenus.remove"].customCallback =
960 function(name, request, response) {
961 if (chrome.extension.lastError) {
962 return;
963 }
964 var id = request.args[0];
965 delete chromeHidden.contextMenus.handlers[id];
966 };
967
968 apiFunctions["contextMenus.update"].customCallback =
969 function(name, request, response) {
970 if (chrome.extension.lastError) {
971 return;
972 }
973 var id = request.args[0];
974 if (request.args[1].onclick) {
975 chromeHidden.contextMenus.handlers[id] = request.args[1].onclick;
976 }
977 };
978
979 apiFunctions["contextMenus.removeAll"].customCallback =
980 function(name, request, response) {
981 if (chrome.extension.lastError) {
982 return;
983 }
984 chromeHidden.contextMenus.handlers = {};
985 };
986
987 apiFunctions["tabs.captureVisibleTab"].updateArgumentsPreValidate =
988 function() {
989 // Old signature:
990 // captureVisibleTab(int windowId, function callback);
991 // New signature:
992 // captureVisibleTab(int windowId, object details, function callback);
993 //
994 // TODO(skerner): The next step to omitting optional arguments is the
995 // replacement of this code with code that matches arguments by type.
996 // Once this is working for captureVisibleTab() it can be enabled for
997 // the rest of the API. See crbug/29215 .
998 if (arguments.length == 2 && typeof(arguments[1]) == "function") {
999 // If the old signature is used, add a null details object.
1000 newArgs = [arguments[0], null, arguments[1]];
1001 } else {
1002 newArgs = arguments;
1003 }
1004 return newArgs;
1005 };
1006
1007 apiFunctions["omnibox.sendSuggestions"].updateArgumentsPostValidate =
1008 function(requestId, userSuggestions) {
1009 var suggestions = [];
1010 for (var i = 0; i < userSuggestions.length; i++) {
1011 var parseResult = parseOmniboxDescription(
1012 userSuggestions[i].description);
1013 parseResult.content = userSuggestions[i].content;
1014 suggestions.push(parseResult);
1015 }
1016 return [requestId, suggestions];
1017 };
1018
1019 apiFunctions["tts.speak"].handleRequest = function() {
1020 var args = arguments;
1021 if (args.length > 1 && args[1] && args[1].onEvent) {
1022 var id = GetNextTtsEventId();
1023 args[1].srcId = id;
1024 chromeHidden.tts.handlers[id] = args[1].onEvent;
1025 }
1026 sendRequest(this.name, args, this.definition.parameters);
1027 return id;
1028 };
1029
1030 if (chrome.test) {
1031 chrome.test.getApiDefinitions = GetExtensionAPIDefinition;
1032 }
1033
1034 setupPageActionEvents(extensionId);
1035 setupHiddenContextMenuEvent(extensionId);
1036 setupInputEvents();
1037 setupOmniboxEvents();
1038 setupTtsEvents();
1039 });
1040
1041 if (!chrome.experimental)
1042 chrome.experimental = {};
1043
1044 if (!chrome.experimental.accessibility)
1045 chrome.experimental.accessibility = {};
1046
1047 if (!chrome.experimental.speechInput)
1048 chrome.experimental.speechInput = {};
1049
1050 if (!chrome.tts)
1051 chrome.tts = {};
1052
1053 if (!chrome.ttsEngine)
1054 chrome.ttsEngine = {};
1055
1056 if (!chrome.experimental.downloads)
1057 chrome.experimental.downloads = {};
1058 })();
OLDNEW
« no previous file with comments | « chrome/renderer/resources/extensions/event.js ('k') | chrome/renderer/resources/extensions/miscellaneous_bindings.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698