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

Side by Side Diff: chrome/common/extensions/docs/examples/api/omnibox/extension-docs/background.html

Issue 8309001: Adding `content_security_policy` to a few sample extensions. (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: License and whitespace. 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
1 <!DOCTYPE html> 1 <!DOCTYPE html>
2 <!-- 2 <!--
3 * Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this 3 * Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this
4 * source code is governed by a BSD-style license that can be found in the 4 * source code is governed by a BSD-style license that can be found in the
5 * LICENSE file. 5 * LICENSE file.
6 --> 6 -->
7 <html> 7 <html>
8 <head> 8 <head>
9 </head> 9 </head>
10 <body> 10 <body>
11 <script> 11 <script src="background.js"></script>
12 /**
13 * Allows for binding callbacks to a specific scope.
14 * @param {Object} scope Scope to bind to.
15 * @returns {Function} A wrapped call to this function.
16 */
17 Function.prototype.bind = function(scope) {
18 var func = this;
19 return function() {
20 return func.apply(scope, arguments);
21 };
22 };
23
24 //////////////////////////////////////////////////////////////////////////
25
26 /**
27 * Holds the search index and exposes operations to search the API docs.
28 * @constructor
29 */
30 function APISearchCorpus() {
31 this.corpus_ = [];
32 };
33
34 /**
35 * Adds an entry to the index.
36 * @param {String} name Name of the function (e.g. chrome.tabs.get).
37 * @param {String} url Url to the documentation.
38 * @param {String} desc Description (optional).
39 * @param {String} type The type of entry (e.g. method, event).
40 */
41 APISearchCorpus.prototype.addEntry = function(name, url, desc, type) {
42 this.corpus_.push({
43 'name' : name,
44 'url' : url,
45 'style' : name,
46 'description' : desc,
47 'type' : type
48 });
49 };
50
51 /**
52 * Locates a match from the supplied keywords against text.
53 *
54 * Keywords are matched in the order supplied, and a non-overlapping
55 * search is used. The matches are returned in a styled string that
56 * can be passed directly to the omnibox API.
57 *
58 * @param {Array.<String>} keywords A list of keywords to check.
59 * @param {String} name The name to search against.
60 * @returns {String|null} A string containing &lt;match&gt; markup
61 * corresponding to the matched text, or null if no match was found.
62 */
63 APISearchCorpus.prototype.findMatch_ = function(keywords, name) {
64 var style = [];
65 var indexFrom = 0;
66 var lowerName = name.toLowerCase();
67 for (var i = 0; i < keywords.length; i++) {
68 var keyword = keywords[i].toLowerCase();
69 var start = lowerName.indexOf(keyword, indexFrom);
70 if (start == -1) {
71 return null;
72 }
73 var end = start + keyword.length + 1;
74
75 style.push(name.substring(indexFrom, start))
76 style.push('<match>');
77 style.push(name.substring(start, end));
78 style.push('</match>');
79
80 indexFrom = end;
81 }
82 style.push(name.substring(indexFrom));
83 return style.join('');
84 };
85
86 /**
87 * Searches this corpus for the supplied text.
88 * @param {String} text Query text.
89 * @param {Number} limit Max results to return.
90 * @returns {Array.<Object>} A list of entries corresponding with
91 * matches (@see APISearchCorpus.findMatch_ for keyword search
92 * algorithm. Results are returned in a sorted order, first by
93 * length, then alphabetically by name. An exact match will be
94 * returned first.
95 */
96 APISearchCorpus.prototype.search = function(text, limit) {
97 var results = [];
98 var match = null;
99 if (!text || text.length == 0) {
100 return this.corpus_.slice(0, limit); // No text, start listing APIs.
101 }
102 var searchText = text.toLowerCase();
103 var keywords = searchText.split(' ');
104 for (var i = 0; i < this.corpus_.length; i++) {
105 var name = this.corpus_[i]['name'];
106 if (results.length < limit) {
107 var result = this.findMatch_(keywords, name);
108 if (result) {
109 this.corpus_[i]['style'] = result;
110 results.push(this.corpus_[i]);
111 }
112 }
113 if (!match && searchText == name) {
114 match = this.corpus_[i]; // An exact match.
115 }
116 if (match && results.length >= limit) {
117 break; // Have an exact match and have reached the search limit.
118 }
119 }
120 if (match) {
121 results.unshift(match); // Add any exact match to the front.
122 }
123 return results;
124 };
125
126 /**
127 * Sorts the corpus according to name length, then name alphabetically.
128 */
129 APISearchCorpus.prototype.sort = function() {
130 function compareLength(a, b) {
131 return a['name'].length - b['name'].length;
132 };
133
134 function compareAlpha(a, b) {
135 if (a['name'] < b['name']) return -1;
136 if (a['name'] > b['name']) return 1;
137 return 0;
138 };
139
140 function compare(a, b) {
141 var result = compareLength(a, b);
142 if (result == 0) result = compareAlpha(a, b);
143 return result;
144 };
145
146 this.corpus_.sort(compare);
147 };
148
149 //////////////////////////////////////////////////////////////////////////
150
151 /**
152 * Provides an interface to the Chrome Extensions documentation site.
153 * @param {APISearchCorpus} corpus The search corpus to populate.
154 * @constructor
155 */
156 function DocsManager(corpus) {
157 this.CODE_URL_PREFIX = 'http://code.google.com/chrome/extensions/';
158 this.API_MANIFEST_URL = [
159 'http://src.chromium.org/viewvc/chrome/trunk/src/',
160 'chrome/common/extensions/api/extension_api.json'
161 ].join('');
162 this.corpus_ = corpus;
163 };
164
165 /**
166 * Initiates a fetch of the docs and populates the corpus.
167 */
168 DocsManager.prototype.fetch = function() {
169 this.fetchApi_(this.onApi_.bind(this));
170 };
171
172 /**
173 * Retrieves the API manifest from cache or fetches a new one if none.
174 * @param {Function} callback The function to pass the parsed manifest
175 * data to.
176 */
177 DocsManager.prototype.fetchApi_ = function(callback) {
178 var currentCacheTime = this.getCacheTime_();
179 if (localStorage['cache-time'] && localStorage['cache']) {
180 var cacheTime = JSON.parse(localStorage['cache-time']);
181 if (cacheTime < currentCacheTime) {
182 callback(JSON.parse(localStorage['cache']));
183 return;
184 }
185 }
186 var xhr = new XMLHttpRequest();
187 xhr.addEventListener('readystatechange', function(evt) {
188 if (xhr.readyState == 4 && xhr.responseText) {
189 localStorage['cache-time'] = JSON.stringify(currentCacheTime);
190 localStorage['cache'] = xhr.responseText;
191 var json = JSON.parse(xhr.responseText);
192 callback(json);
193 }
194 });
195 xhr.open('GET', this.API_MANIFEST_URL, true);
196 xhr.send();
197 };
198
199 /**
200 * Returns a time which can be used to cache a manifest response.
201 * @returns {Number} A timestamp divided by the number of ms in a day,
202 * rounded to the nearest integer. This means the number should
203 * change only once per day, invalidating the cache that often.
204 */
205 DocsManager.prototype.getCacheTime_ = function() {
206 var time = new Date().getTime();
207 time = Math.round(time / (1000 * 60 * 60 * 24));
208 return time;
209 };
210
211 /**
212 * Returns an URL for the documentation given an API element.
213 * @param {String} namespace The namespace (e.g. tabs, windows).
214 * @param {String} type The type of element (e.g. event, method, type).
215 * @param {String} name The name of the element (e.g. onRemoved).
216 * @returns {String} An URL corresponding with the documentation for the
217 * given element.
218 */
219 DocsManager.prototype.getDocLink_ = function(namespace, type, name) {
220 var linkparts = [ this.CODE_URL_PREFIX, namespace, '.html' ];
221 if (type && name) {
222 linkparts.push('#', type, '-', name);
223 }
224 return linkparts.join('');
225 };
226
227 /**
228 * Returns a qualified name for an API element.
229 * @param {String} namespace The namespace (e.g. tabs, windows).
230 * @param {String} name The name of the element (e.g. onRemoved).
231 * @returns {String} A qualified API name (e.g. chrome.tabs.onRemoved).
232 */
233 DocsManager.prototype.getName_ = function(namespace, name) {
234 var nameparts = [ 'chrome', namespace ];
235 if (name) {
236 nameparts.push(name);
237 }
238 return nameparts.join('.');
239 };
240
241 /**
242 * Parses an API manifest data structure and populates the search index.
243 * @param {Object} api The api manifest, as a JSON-parsed object.
244 */
245 DocsManager.prototype.onApi_ = function(api) {
246 for (var i = 0; i < api.length; i++) {
247 var module = api[i];
248 if (module.nodoc) {
249 continue;
250 }
251 var ns = module.namespace;
252 var nsName = this.getName_(ns);
253 var nsUrl = this.getDocLink_(ns);
254 this.corpus_.addEntry(nsName, nsUrl, null, 'namespace');
255 this.parseAPIArray_('method', ns, module.functions);
256 this.parseAPIArray_('event', ns, module.events);
257 this.parseAPIArray_('type', ns, module.types);
258 this.parseAPIObject_('property', ns, module.properties);
259 this.corpus_.sort();
260 }
261 };
262
263 /**
264 * Parses an API manifest subsection which is formatted as an Array.
265 * @param {String} type The type of data (e.g. method, event, type).
266 * @param {String} ns The namespace (e.g. tabs, windows).
267 * @param {Array} list The list of API elements.
268 */
269 DocsManager.prototype.parseAPIArray_ = function(type, ns, list) {
270 if (!list) return;
271 for (var j = 0; j < list.length; j++) {
272 var item = list[j];
273 if (item.nodoc) continue;
274 var name = item.name || item.id;
275 var fullname = this.getName_(ns, name);
276 var url = this.getDocLink_(ns, type, name);
277 var description = item.description;
278 this.corpus_.addEntry(fullname, url, description, type);
279 }
280 };
281
282 /**
283 * Parses an API manifest subsection which is formatted as an Object.
284 * @param {String} type The type of data (e.g. property).
285 * @param {String} ns The namespace (e.g. tabs, windows).
286 * @param {Object} list The object containing API elements.
287 */
288 DocsManager.prototype.parseAPIObject_ = function(type, ns, list) {
289 for (var prop in list) {
290 if (list.hasOwnProperty(prop)) {
291 var name = this.getName_(ns, prop);
292 var url = this.getDocLink_(ns, type, prop);
293 var description = list[prop].description;
294 this.corpus_.addEntry(name, url, description, type);
295 }
296 }
297 };
298
299 //////////////////////////////////////////////////////////////////////////
300
301 /**
302 * Manages text input into the omnibox and returns search results.
303 * @param {APISearchCorpus} Populated search corpus.
304 * @param {TabManager} Manager to use to open tabs.
305 * @constructor
306 */
307 function OmniboxManager(corpus, tabManager) {
308 this.SEPARATOR = ' - ';
309 this.corpus_ = corpus;
310 this.tabManager_ = tabManager;
311 chrome.omnibox.onInputChanged.addListener(
312 this.onChanged_.bind(this));
313 chrome.omnibox.onInputEntered.addListener(
314 this.onEntered_.bind(this));
315 };
316
317 /**
318 * Converts a corpus match to an object suitable for the omnibox API.
319 * @param {Object} match The match to convert.
320 * @returns {Object} A suggestion object formatted for the omnibox API.
321 */
322 OmniboxManager.prototype.convertMatchToSuggestion_ = function(match) {
323 var suggestion = [ match['style'] ];
324 if (match['type']) {
325 // Abusing the URL style a little, but want this to stand out.
326 suggestion.push(['<url>', match['type'], '</url>'].join(''));
327 }
328 if (match['description']) {
329 suggestion.push(['<dim>', match['description'], '</dim>'].join(''));
330 }
331 return {
332 'content' : match['name'],
333 'description' : suggestion.join(' - ')
334 }
335 };
336
337 /**
338 * Suggests a list of possible matches when omnibox text changes.
339 * @param {String} text Text input from the omnibox.
340 * @param {Function} suggest Callback to execute with a list of
341 * suggestion objects, if any matches were found.
342 */
343 OmniboxManager.prototype.onChanged_ = function(text, suggest) {
344 var matches = this.corpus_.search(text, 10);
345 var suggestions = [];
346 for (var i = 0; i < matches.length; i++) {
347 var suggestion = this.convertMatchToSuggestion_(matches[i]);
348 suggestions.push(suggestion);
349 }
350 suggest(suggestions);
351 };
352
353 /**
354 * Opens the most appropriate URL when enter is pressed in the omnibox.
355 *
356 * Note that the entered text does not have to be exact - the first
357 * search result is automatically opened when enter is pressed.
358 *
359 * @param {String} text The text entered.
360 */
361 OmniboxManager.prototype.onEntered_ = function(text) {
362 var matches = this.corpus_.search(text, 1);
363 if (matches.length > 0) {
364 this.tabManager_.open(matches[0]['url']);
365 }
366 };
367
368 //////////////////////////////////////////////////////////////////////////
369
370 /**
371 * Manages opening urls in tabs.
372 * @constructor
373 */
374 function TabManager() {
375 this.tab_ = null;
376 chrome.tabs.onRemoved.addListener(this.onRemoved_.bind(this));
377 };
378
379 /**
380 * When a tab is removed, see if it was opened by us and null out if yes.
381 * @param {Number} tabid ID of the removed tab.
382 */
383 TabManager.prototype.onRemoved_ = function(tabid) {
384 if (this.tab_ && tabid == this.tab_.id) this.tab_ = null;
385 };
386
387 /**
388 * When a tab opened by us is created, store it for future updates.
389 * @param {Tab} tab The tab which was just opened.
390 */
391 TabManager.prototype.onTab_ = function(tab) {
392 this.tab_ = tab;
393 };
394
395 /**
396 * Opens the supplied URL.
397 *
398 * The first time this method is called a new tab is created. Subsequent
399 * times this is called, the opened tab will be updated. If that tab
400 * is ever closed, then a new tab will be created for the next call.
401 *
402 * @param {String} url The URL to open.
403 */
404 TabManager.prototype.open = function(url) {
405 if (url) {
406 var args = { 'url' : url, 'selected' : true };
407 if (this.tab_) {
408 chrome.tabs.update(this.tab_.id, args);
409 } else {
410 chrome.tabs.create(args, this.onTab_.bind(this));
411 }
412 }
413 };
414
415 //////////////////////////////////////////////////////////////////////////
416
417 var corpus = new APISearchCorpus();
418 var docsManager = new DocsManager(corpus);
419 docsManager.fetch();
420 var tabManager = new TabManager();
421 var omnibox = new OmniboxManager(corpus, tabManager);
422 </script>
423 </body> 12 </body>
424 </html> 13 </html>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698