1 // Copyright (c) 2012 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.
5 // Custom binding for the omnibox API. Only injected into the v8 contexts
6 // for extensions which have permission for the omnibox API.
8 var binding = require('binding').Binding.create('omnibox');
10 var eventBindings = require('event_bindings');
11 var sendRequest = require('sendRequest').sendRequest;
13 // Remove invalid characters from |text| so that it is suitable to use
14 // for |AutocompleteMatch::contents|.
15 function sanitizeString(text, shouldTrim) {
16 // NOTE: This logic mirrors |AutocompleteMatch::SanitizeString()|.
17 // 0x2028 = line separator; 0x2029 = paragraph separator.
18 var kRemoveChars = /(\r|\n|\t|\u2028|\u2029)/gm;
20 text = text.trimLeft();
21 return text.replace(kRemoveChars, '');
24 // Parses the xml syntax supported by omnibox suggestion results. Returns an
25 // object with two properties: 'description', which is just the text content,
26 // and 'descriptionStyles', which is an array of style objects in a format
27 // understood by the C++ backend.
28 function parseOmniboxDescription(input) {
29 var domParser = new DOMParser();
31 // The XML parser requires a single top-level element, but we want to
32 // support things like 'hello, <match>world</match>!'. So we wrap the
33 // provided text in generated root level element.
34 var root = domParser.parseFromString(
35 '<fragment>' + input + '</fragment>', 'text/xml');
37 // DOMParser has a terrible error reporting facility. Errors come out nested
38 // inside the returned document.
39 var error = root.querySelector('parsererror div');
41 throw new Error(error.textContent);
44 // Otherwise, it's valid, so build up the result.
50 // Recursively walk the tree.
52 for (var i = 0, child; child = node.childNodes[i]; i++) {
53 // Append text nodes to our description.
54 if (child.nodeType == Node.TEXT_NODE) {
55 var shouldTrim = result.description.length == 0;
56 result.description += sanitizeString(child.nodeValue, shouldTrim);
60 // Process and descend into a subset of recognized tags.
61 if (child.nodeType == Node.ELEMENT_NODE &&
62 (child.nodeName == 'dim' || child.nodeName == 'match' ||
63 child.nodeName == 'url')) {
65 'type': child.nodeName,
66 'offset': result.description.length
68 $Array.push(result.descriptionStyles, style);
70 style.length = result.description.length - style.offset;
74 // Descend into all other nodes, even if they are unrecognized, for
84 binding.registerCustomHook(function(bindingsAPI) {
85 var apiFunctions = bindingsAPI.apiFunctions;
87 apiFunctions.setUpdateArgumentsPreValidate('setDefaultSuggestion',
88 function(suggestResult) {
89 if (suggestResult.content != undefined) { // null, etc.
91 'setDefaultSuggestion cannot contain the "content" field');
93 return [suggestResult];
96 apiFunctions.setHandleRequest('setDefaultSuggestion', function(details) {
97 var parseResult = parseOmniboxDescription(details.description);
98 sendRequest(this.name, [parseResult], this.definition.parameters);
101 apiFunctions.setUpdateArgumentsPostValidate(
102 'sendSuggestions', function(requestId, userSuggestions) {
103 var suggestions = [];
104 for (var i = 0; i < userSuggestions.length; i++) {
105 var parseResult = parseOmniboxDescription(
106 userSuggestions[i].description);
107 parseResult.content = userSuggestions[i].content;
108 $Array.push(suggestions, parseResult);
110 return [requestId, suggestions];
114 eventBindings.registerArgumentMassager('omnibox.onInputChanged',
115 function(args, dispatch) {
117 var requestId = args[1];
118 var suggestCallback = function(suggestions) {
119 chrome.omnibox.sendSuggestions(requestId, suggestions);
121 dispatch([text, suggestCallback]);
124 exports.binding = binding.generate();