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();