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

Side by Side Diff: chrome/browser/resources/history.html

Issue 12418: Implement History as HTML and add/change a bunch of stuff to make it easier t... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 12 years 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 <!DOCTYPE HTML>
2 <html id="t">
3 <head>
4 <meta charset="utf-8">
5 <title jscontent="title"></title>
6 <script type="text/javascript">
7 ///////////////////////////////////////////////////////////////////////////////
8 // Globals:
9 var RESULTS_PER_PAGE = 60;
10 var MAX_SEARCH_DEPTH = 18;
11
12 // Amount of time between pageviews that we consider a 'break' in browsing,
13 // measured in milliseconds.
14 var BROWSING_GAP_TIME = 15 * 60 * 1000;
15
16 function $(o) {return document.getElementById(o);}
17
18 // TODO(glen): Get rid of these global references, replace with a controller
19 // or just make the classes own more of the page.
20 var historyModel;
21 var historyView;
22 var localStrings;
23 var pageState;
24
25 ///////////////////////////////////////////////////////////////////////////////
26 // localStrings:
27 /**
28 * We get strings into the page by using JSTemplate to populate some elements
29 * with localized content, then reading the content of those elements into
30 * this global strings object.
31 * @param {Node} node The DOM node containing all our strings.
32 */
33 function LocalStrings(node) {
34 this.strings_ = {};
35
36 var children = node.childNodes;
37 for (var i = 0, child; child = children[i]; i++) {
38 var id = child.id;
39 if (id) {
40 this.strings_[id] = child.innerHTML;
41 }
42 }
43 }
44
45 /**
46 * Gets a localized string by its id.
47 * @param {string} s The id of the string we want
48 * @return {string} The localized string
49 */
50 LocalStrings.prototype.getString = function(s) {
51 return (s in this.strings_) ? this.strings_[s] : '';
52 }
53
54 /**
55 * Returns a formatted localized string (where all %s contents are replaced
56 * by the second argument).
57 * @param {string} s The id of the string we want
58 * @param {string} d The string to include in the formatted string
59 * @return {string} The formatted string.
60 */
61 LocalStrings.prototype.formatString = function(s, d) {
62 return (s in this.strings_) ? this.strings_[s].replace(/\%s/, d) : '';
63 }
64
65 ///////////////////////////////////////////////////////////////////////////////
66 // Page:
67 /**
68 * Class to hold all the information about an entry in our model.
69 * @param {Object} result An object containing the page's data.
70 * @param {boolean} continued Whether this page is on the same day as the
71 * page before it
72 */
73 function Page(result, continued, model) {
74 this.model_ = model;
75 this.title_ = result.title;
76 this.url_ = result.url;
77 this.snippet_ = result.snippet || "";
78
79 // All the date information is public so that owners can compare properties of
80 // two items easily.
81
82 // We get the time in seconds, but we want it in milliseconds.
83 this.time = new Date(result.time * 1000);
84
85 // See comment in BrowsingHistoryHandler::QueryComplete - we won't always
86 // get all of these.
87 this.dateRelativeDay = result.dateRelativeDay || "";
88 this.dateTimeOfDay = result.dateTimeOfDay || "";
89 this.dateShort = result.dateShort || "";
90
91 // Whether this is the continuation of a previous day.
92 this.continued = continued;
93 }
94
95 // Page, Public: --------------------------------------------------------------
96 /**
97 * @return {string} Gets the HTML representation of the page
98 * for use in browse results.
99 */
100 Page.prototype.getBrowseResultHTML = function() {
101 return '<div class="entry">' +
102 '<div class="time">' +
103 this.dateTimeOfDay +
104 '</div>' +
105 this.getTitleHTML_() +
106 '</div>';
107 }
108
109 /**
110 * @return {string} Gets the HTML representation of the page for
111 * use in search results.
112 */
113 Page.prototype.getSearchResultHTML = function() {
114 return ['<tr class="entry"><td valign="top">',
115 '<div class="time">',
116 this.dateShort,
117 '</div>',
118 '</td><td valign="top">',
119 this.getTitleHTML_(),
120 '<div class="snippet">',
121 this.getHighlightedSnippet_(),
122 '</div>',
123 '</td></tr>'].join("");
124 }
125
126 // Page, private: -------------------------------------------------------------
127 /**
128 * @return {string} The page's snippet highlighted with the model's
129 * current search term.
130 */
131 Page.prototype.getHighlightedSnippet_ = function() {
132 return Page.getHighlightedText_(this.snippet_, this.model_.getSearchText());
133 }
134
135 /**
136 * @return {string} Gets the page's title highlighted with the
137 * model's current search term.
138 */
139 Page.prototype.getHighlightedTitle_ = function() {
140 return Page.getHighlightedText_(this.title_, this.model_.getSearchText());
141 }
142
143 /**
144 * @return {string} HTML for the title block.
145 */
146 Page.prototype.getTitleHTML_ = function() {
147 return '<div class="title">' +
148 '<a ' +
149 'href="' + this.url_ + '" ' +
150 'style="background-image:url(chrome://favicon/' +
151 this.url_ + ')" ' +
152 '>' +
153 this.getHighlightedTitle_() +
154 '</a>' +
155 '</div>';
156 }
157
158 // Page, private, static: -----------------------------------------------------
159 /**
160 * Case-insensitively highlights a string.
161 * @param {string} str The source string
162 * @param {string} opt_highlight The string to highlight with
163 * @return {string} The highlighted string
164 */
165 Page.getHighlightedText_ = function(str, opt_highlight ) {
166 if (!opt_highlight) return str;
167
168 var r = new RegExp(Page.pregQuote_(opt_highlight), 'gim');
169 return str.replace(r, "<b>\$&</b>");
170 }
171
172 /**
173 * Quote a string so it can be used in a regular expression.
174 * @param {string} str The source string
175 * @return {string} The escaped string
176 */
177 Page.pregQuote_ = function(str) {
178 return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
179 }
180
181 ///////////////////////////////////////////////////////////////////////////////
182 // HistoryModel:
183 /**
184 * Global container for history data. Future optimizations might include
185 * allowing the creation of a HistoryModel for each search string, allowing
186 * quick flips back and forth between results.
187 *
188 * The history model is based around pages, and only fetching the data to
189 * fill the currently requested page. This is somewhat dependent on the view,
190 * and so future work may wish to change history model to operate on
191 * timeframe (day or week) based containers.
192 */
193 function HistoryModel() {
194 this.clearModel_();
195 this.view_;
196 }
197
198 // HistoryModel, Public: ------------------------------------------------------
199 /**
200 * Sets our current view that is called when the history model changes.
201 * @param {HistoryView} view The view to set our current view to.
202 */
203 HistoryModel.prototype.setView = function(view) {
204 this.view_ = view;
205 }
206
207 /**
208 * Start a new search - this will clear out our model.
209 * @param {String} searchText The text to search for
210 * @param {Number} opt_page The page to view - this is mostly used when setting
211 * up an initial view, use #requestPage otherwise.
212 */
213 HistoryModel.prototype.setSearchText = function(searchText, opt_page) {
214 this.clearModel_();
215 this.searchText_ = searchText;
216 this.requestedPage_ = opt_page ? opt_page : 0;
217 this.getSearchResults_();
218 }
219
220 /**
221 * @return {String} The current search text.
222 */
223 HistoryModel.prototype.getSearchText = function() {
224 return this.searchText_;
225 }
226
227 /**
228 * Tell the model that the view will want to see the current page. When
229 * the data becomes available, the model will call the view back.
230 * @page {Number} page The page we want to view.
231 */
232 HistoryModel.prototype.requestPage = function(page) {
233 this.requestedPage_ = page;
234 this.updateSearch_();
235 }
236
237 /**
238 * Receiver for history query.
239 * @param {String} term The search term that the results are for.
240 * @param {Array} results A list of results
241 */
242 HistoryModel.prototype.addResults = function(term, results) {
243 this.inFlight_ = false;
244 if (term != this.searchText_) {
245 // If our results aren't for our current search term, they're rubbish.
246 return;
247 }
248
249 // Currently we assume we're getting things in date order. This needs to
250 // be updated if that ever changes.
251 if (results) {
252 var lastURL, lastDay;
253 var oldLength = this.pages_.length;
254 if (oldLength) {
255 var oldPage = this.pages_[oldLength - 1];
256 lastURL = oldPage.url;
257 lastDay = oldPage.dateRelativeDay;
258 }
259
260 for (var i = 0, thisResult; thisResult = results[i]; i++) {
261 var thisURL = thisResult.url;
262 var thisDay = thisResult.dateRelativeDay;
263
264 // Remove adjacent duplicates.
265 if (!lastURL || lastURL != thisURL) {
266 // Figure out if this page is in the same day as the previous page,
267 // this is used to determine how day headers should be drawn.
268 this.pages_.push(new Page(thisResult, thisDay == lastDay, this));
269 lastDay = thisDay;
270 lastURL = thisURL;
271 }
272 }
273 }
274
275 this.updateSearch_();
276 }
277
278 /**
279 * @return {Number} The number of pages in the model.
280 */
281 HistoryModel.prototype.getSize = function() {
282 return this.pages_.length;
283 }
284
285 /**
286 * @return {boolean} Whether our history query has covered all of
287 * the user's history
288 */
289 HistoryModel.prototype.isComplete = function() {
290 return this.complete_;
291 }
292
293 /**
294 * Get a list of pages between specified index positions.
295 * @param {Number} start The start index
296 * @param {Number} end The end index
297 * @return {Array} A list of pages
298 */
299 HistoryModel.prototype.getNumberedRange = function(start, end) {
300 if (start >= this.getSize())
301 return [];
302
303 var end = end > this.getSize() ? this.getSize() : end;
304 return this.pages_.slice(start, end);
305 }
306
307 // HistoryModel, Private: -----------------------------------------------------
308 HistoryModel.prototype.clearModel_ = function() {
309 this.inFlight_ = false; // Whether a query is inflight.
310 this.searchText_ = '';
311 this.searchMonth_ = 0;
312 this.pages_ = []; // Date-sorted list of pages.
313
314 // The page that the view wants to see - we only fetch slightly past this
315 // point. If the view requests a page that we don't have data for, we try
316 // to fetch it and call back when we're done.
317 this.requestedPage_ = 0;
318
319 this.complete_ = false;
320 }
321
322 /**
323 * Figure out if we need to do more searches to fill the currently requested
324 * page. If we think we can fill the page, call the view and let it know
325 * we're ready to show something.
326 */
327 HistoryModel.prototype.updateSearch_ = function() {
328 if (this.searchMonth_ >= MAX_SEARCH_DEPTH) {
329 // We have maxed out. There will be no more data.
330 this.complete_ = true;
331 this.view_.onModelReady();
332 } else {
333 // If we can't fill the requested page, ask for more data unless a request
334 // is still in-flight.
335 if (!this.canFillPage_(this.requestedPage_) && !this.inFlight_) {
336 this.getSearchResults_(this.searchMonth_ + 1);
337 }
338
339 // If we have any data for the requested page, show it.
340 if (this.haveDataForPage_(this.requestedPage_)) {
341 this.view_.onModelReady();
342 }
343 }
344 }
345
346 /**
347 * Get search results for a selected month. Our history system is optimized
348 * for queries that don't cross month boundaries.
349 *
350 * TODO: Fix this for when the user's clock goes across month boundaries.
351 * @param {number} opt_month How many months back to do the search.
352 */
353 HistoryModel.prototype.getSearchResults_ = function(opt_month) {
354 this.searchMonth_ = opt_month || 0;
355 chrome.send('getHistory',
356 [this.searchText_, String(this.searchMonth_)]);
357 this.inFlight_ = true;
358 }
359
360 /**
361 * Check to see if we have data for a given page.
362 * @param {number} page The page number
363 * @return {boolean} Whether we have any data for the given page.
364 */
365 HistoryModel.prototype.haveDataForPage_ = function(page) {
366 return (page * RESULTS_PER_PAGE < this.getSize());
367 }
368
369 /**
370 * Check to see if we have data to fill a page.
371 * @param {number} page The page number.
372 * @return {boolean} Whether we have data to fill the page.
373 */
374 HistoryModel.prototype.canFillPage_ = function(page) {
375 return ((page + 1) * RESULTS_PER_PAGE <= this.getSize());
376 }
377
378 ///////////////////////////////////////////////////////////////////////////////
379 // HistoryView:
380 /**
381 * Functions and state for populating the page with HTML. This should one-day
382 * contain the view and use event handlers, rather than pushing HTML out and
383 * getting called externally.
384 * @param {HistoryModel} model The model backing this view.
385 */
386 function HistoryView(model) {
387 this.summaryDiv_ = $('results-summary');
388 this.summaryDiv_.innerHTML = localStrings.getString('loading');
389 this.resultDiv_ = $('results-display');
390 this.pageDiv_ = $('results-pagination');
391 this.model_ = model
392 this.pageIndex_ = 0;
393
394 this.model_.setView(this);
395 }
396
397 // HistoryView, public: -------------------------------------------------------
398 /**
399 * Do a search and optionally view a certain page.
400 * @param {string} term The string to search for.
401 * @param {number} opt_page The page we wish to view, only use this for
402 * setting up initial views, as this triggers a search.
403 */
404 HistoryView.prototype.setSearch = function(term, opt_page) {
405 this.pageIndex_ = parseInt(opt_page || 0, 10);
406 window.scrollTo(0, 0);
407 this.model_.setSearchText(term, this.pageIndex_);
408 pageState.setUIState(term, this.pageIndex_);
409 }
410
411 /**
412 * Switch to a specified page.
413 * @param {string} term The string to search for.
414 * @param {number} opt_page The page we wish to view.
415 */
416 HistoryView.prototype.setPage = function(page) {
417 this.pageIndex_ = parseInt(page);
418 window.scrollTo(0, 0);
419 this.model_.requestPage(page);
420 pageState.setUIState(this.model_.getSearchText(), this.pageIndex_);
421 }
422
423 /**
424 * @return {number} The page number being viewed.
425 */
426 HistoryView.prototype.getPage = function() {
427 return this.pageIndex_;
428 }
429
430 /**
431 * Callback for the history model to let it know that it has data ready for us
432 * to view.
433 */
434 HistoryView.prototype.onModelReady = function() {
435 this.displayResults_();
436 }
437
438 // HistoryView, private: ------------------------------------------------------
439 /**
440 * Update the page with results.
441 */
442 HistoryView.prototype.displayResults_ = function() {
443 var output = [];
444 var results = this.model_.getNumberedRange(
445 this.pageIndex_ * RESULTS_PER_PAGE,
446 this.pageIndex_ * RESULTS_PER_PAGE + RESULTS_PER_PAGE);
447
448 if (this.model_.getSearchText()) {
449 output.push('<table class="results" cellspacing="0" ',
450 'cellpadding="0" border="0">');
451 for (var i = 0, page; page = results[i]; i++) {
452 output.push(page.getSearchResultHTML());
453 }
454 output.push('</table>');
455 } else {
456 var lastTime = Math.infinity;
457 for (var i = 0, page; page = results[i]; i++) {
458 // Break across day boundaries and insert gaps for browsing pauses.
459 var thisTime = page.time.getTime();
460 if (page.continued && i == 0) {
461 output.push('<div class="day">' + page.dateRelativeDay + ' ' +
462 localStrings.getString('cont') + '</div>');
463 } else if (!page.continued) {
464 output.push('<div class="day">' + page.dateRelativeDay + '</div>');
465 } else if (lastTime - thisTime > BROWSING_GAP_TIME) {
466 output.push('<div class="gap"></div>');
467 }
468 lastTime = thisTime;
469
470 // Draw entry.
471 output.push(page.getBrowseResultHTML());
472 }
473 }
474 this.resultDiv_.innerHTML = output.join("");
475
476 this.displaySummaryBar_();
477 this.displayNavBar_();
478 }
479
480 /**
481 * Update the summary bar with descriptive text.
482 */
483 HistoryView.prototype.displaySummaryBar_ = function() {
484 var searchText = this.model_.getSearchText();
485 if (searchText != '') {
486 this.summaryDiv_.innerHTML = localStrings.formatString('searchresultsfor',
487 searchText);
488 } else {
489 this.summaryDiv_.innerHTML = localStrings.getString('history');
490 }
491 }
492
493 /**
494 * Update the pagination tools.
495 */
496 HistoryView.prototype.displayNavBar_ = function() {
497 var navOutput = [];
498 if (this.pageIndex_ > 0) {
499 navOutput.push(this.createPageNavHTML_(0,
500 localStrings.getString('newest')));
501 navOutput.push(this.createPageNavHTML_(
502 this.pageIndex_ - 1, localStrings.getString('newer')));
503 }
504 if (this.model_.getSize() > (this.pageIndex_ + 1) * RESULTS_PER_PAGE) {
505 navOutput.push(this.createPageNavHTML_(
506 this.pageIndex_ + 1, localStrings.getString('older')));
507 }
508 this.pageDiv_.innerHTML = navOutput.join("");
509 }
510
511 /**
512 * Get the HTML representation of a page navigation link.
513 * @param {number} page The page index the navigation element should link to
514 * @param {string} name The text content of the link
515 * @return {string} HTML representation of the pagination link
516 */
517 HistoryView.prototype.createPageNavHTML_ = function(page, name) {
518 var hashString = PageState.getHashString(this.model_.getSearchText(), page);
519 return '<a href="chrome://history/' +
520 (hashString ? '#' + hashString : '') +
521 '"' +
522 'class="page-navigation"' +
523 'onclick="setPage(' + page + '); return false;"' +
524 '>' + name + '</a>';
525 }
526
527 ///////////////////////////////////////////////////////////////////////////////
528 // State object:
529 /**
530 * An 'AJAX-history' implementation.
531 * @param {HistoryModel} model The model we're representing
532 * @param {HistoryView} view The view we're representing
533 */
534 function PageState(model, view) {
535 // Enforce a singleton.
536 if (PageState.instance) {
537 return PageState.instance;
538 }
539
540 this.model = model;
541 this.view = view;
542
543 if (typeof this.checker_ != 'undefined' && this.checker_) {
544 clearInterval(this.checker_);
545 }
546
547 // TODO(glen): Replace this with a bound method so we don't need
548 // public model and view.
549 this.checker_ = setInterval((function(state_obj) {
550 var hashData = state_obj.getHashData();
551
552 if (hashData.q != state_obj.model.getSearchText(term)) {
553 state_obj.view.setSearch(hashData.q, parseInt(hashData.p));
554 } else if (parseInt(hashData.p) != state_obj.view.getPage()) {
555 state_obj.view.setPage(hashData.p);
556 }
557 }), 50, this);
558 }
559
560 PageState.instance = null;
561
562 /**
563 * @return {Object} An object containing parameters from our window hash.
564 */
565 PageState.prototype.getHashData = function() {
566 var result = {
567 q : '',
568 p : 0
569 };
570
571 if (!window.location.hash) {
572 return result;
573 }
574
575 var hashSplit = window.location.hash.substr(1).split("&");
576 for (var i = 0; i < hashSplit.length; i++) {
577 var pair = hashSplit[i].split("=");
578 if (pair.length > 1) {
579 result[pair[0]] = unescape(pair[1]);
580 }
581 }
582
583 return result;
584 }
585
586 /**
587 * Set the hash to a specified state, this will create an entry in the
588 * session history so the back button cycles through hash states, which
589 * are then picked up by our listener.
590 * @param {string} term The current search string.
591 * @param {string} page The page currently being viewed.
592 */
593 PageState.prototype.setUIState = function(term, page) {
594 // Make sure the form looks pretty.
595 document.forms[0].term.value = term;
596
597 var hash = PageState.getHashString(term, page);
598 if (window.location.hash.substr(1) != hash) {
599 window.location.hash = hash;
600 }
601 }
602
603 /**
604 * Static method to get the hash string for a specified state
605 * @param {string} term The current search string.
606 * @param {string} page The page currently being viewed.
607 * @return {string} The string to be used in a hash.
608 */
609 PageState.getHashString = function(term, page) {
610 var newHash = [];
611 if (term) {
612 newHash.push("q=" + escape(term));
613 }
614 if (page) {
615 newHash.push("p=" + page);
616 }
617
618 return newHash.join("&");
619 }
620
621 ///////////////////////////////////////////////////////////////////////////////
622 // Document Functions:
623 /**
624 * Window onload handler, sets up the page.
625 */
626 function load() {
627 localStrings = new LocalStrings($('l10n'));
628 historyModel = new HistoryModel();
629 historyView = new HistoryView(historyModel);
630 pageState = new PageState(historyModel, historyView);
631
632 // Create default view.
633 var hashData = pageState.getHashData();
634 historyView.setSearch(hashData.q, hashData.p);
635 }
636
637 /**
638 * TODO(glen): Get rid of this function.
639 * Set the history view to a specified page.
640 * @param {String} term The string to search for
641 */
642 function setSearch(term) {
643 if (historyView) {
644 historyView.setSearch(term);
645 }
646 }
647
648 /**
649 * TODO(glen): Get rid of this function.
650 * Set the history view to a specified page.
651 * @param {number} page The page to set the view to.
652 */
653 function setPage(page) {
654 if (historyView) {
655 historyView.setPage(page);
656 }
657 }
658
659 ///////////////////////////////////////////////////////////////////////////////
660 // Chrome callbacks:
661 /**
662 * Our history system calls this function with results from searches.
663 */
664 function historyResult(term, results) {
665 historyModel.addResults(term, results);
666 }
667 </script>
668 <style type="text/css">
669 body {
670 font-family:arial;
671 background-color:white;
672 color:black;
673 font-size:84%;
674 margin:10px;
675 }
676 .header {
677 overflow:auto;
678 clear:both;
679 }
680 .header .logo {
681 float:left;
682 }
683 .header .form {
684 float:left;
685 margin-top:22px;
686 margin-left:12px;
687 }
688 #results-summary {
689 margin-top:12px;
690 border-top:1px solid #9cc2ef;
691 background-color:#ebeff9;
692 font-weight:bold;
693 padding:3px;
694 margin-bottom:-8px;
695 }
696 #results-display {
697 max-width:740px;
698 }
699 .day {
700 margin-top:18px;
701 margin-left:3px;
702 }
703 .gap {
704 margin-left:18px;
705 width:15px;
706 border-right:1px solid #ddd;
707 height:14px;
708 }
709 .entry {
710 margin-left:18px;
711 margin-top:6px;
712 overflow:auto;
713 }
714 table.results {
715 margin-left:4px;
716 }
717 .entry .time {
718 color:#888;
719 float:left;
720 min-width:56px;
721 margin-right:5px;
722 padding-top:1px;
723 }
724 .entry .title {
725 max-width:600px;
726 overflow: hidden;
727 white-space: nowrap;
728 text-overflow: ellipsis;
729 }
730 .results .time, .results .title {
731 margin-top:18px;
732 }
733 .entry .title a {
734 background-repeat:no-repeat;
735 background-size:16px;
736 background-position:0px 1px;
737 padding:1px 0px 4px 22px;
738 }
739 html[dir='rtl'] .entry .title a {
740 background-position:right;
741 padding-left:0px;
742 padding-right:22px;
743 }
744 #results-pagination {
745 padding-top:24px;
746 margin-left:18px;
747 }
748 .page-navigation {
749 padding:8px;
750 background-color:#ebeff9;
751 margin-right:4px;
752 }
753 .footer {
754 height:24px;
755 }
756 </style>
757 </head>
758 <body onload="load();">
759 <div class="header">
760 <a href="" onclick="setSearch(''); return false;">
761 <img src="../../app/theme/history_section.png"
762 width="67" height="67" class="logo" border="0" /></a>
763 <form method="post" action=""
764 onsubmit="setSearch(this.term.value); return false;"
765 class="form">
766 <input type="text" name="term" id="term" />
767 <input type="submit" name="submit" jsvalues="value:searchbutton" />
768 </form>
769 </div>
770 <div class="main">
771 <div id="results-summary"></div>
772 <div id="results-display"></div>
773 <div id="results-pagination"></div>
774 </div>
775 <div class="footer">
776 </div>
777 <div id="l10n" style="display:none;">
778 <span id="loading" jscontent="loading">Loading...</span>
779 <span id="newest" jscontent="newest">&laquo; Newest</span>
780 <span id="newer" jscontent="newer">&#8249; Newer</span>
781 <span id="older" jscontent="older">Older &#8250;</span>
782 <span id="searchresultsfor" jscontent="searchresultsfor">Search results for '% s'</span>
783 <span id="history" jscontent="history">History</span>
784 <span id="cont" jscontent="cont">(cont.)</span>
785 <span id="noresults" jscontent="noresults">No results</span>
786 <span id="noitems" jscontent="noitems">No items</span>
787 <span id="delete" jscontent="delete">delete</span>
788 </div>
789 </body>
790 </html>
OLDNEW
« no previous file with comments | « chrome/browser/resources/browser_resources.vcproj ('k') | chrome/browser/resources/new_tab.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698