summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorarv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-26 22:30:44 +0000
committerarv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-26 22:30:44 +0000
commit86f76ebbe5b1da4438e257663daa40091a5883ee (patch)
tree648b69f05e0a048a6de59e53a00df225d154ee50 /chrome
parent9a1e6d4cfc5028c9c7c431bbf7ed100b28271957 (diff)
downloadchromium_src-86f76ebbe5b1da4438e257663daa40091a5883ee.zip
chromium_src-86f76ebbe5b1da4438e257663daa40091a5883ee.tar.gz
chromium_src-86f76ebbe5b1da4438e257663daa40091a5883ee.tar.bz2
Refactor parts of the NTP to split things into more managable chunks.
BUG=None TEST=Manual Review URL: http://codereview.chromium.org/1759007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@45631 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/browser_resources.grd2
-rw-r--r--chrome/browser/resources/downloads.html2
-rw-r--r--chrome/browser/resources/filebrowse.html2
-rw-r--r--chrome/browser/resources/history.html2
-rw-r--r--chrome/browser/resources/mediaplayer.html10
-rw-r--r--chrome/browser/resources/new_new_tab.html6
-rw-r--r--chrome/browser/resources/new_new_tab.js568
-rw-r--r--chrome/browser/resources/ntp/most_visited.js492
-rw-r--r--chrome/browser/resources/playlist.html4
-rw-r--r--chrome/browser/resources/shared/js/i18n_template.js (renamed from chrome/browser/resources/i18n_template.js)4
-rw-r--r--chrome/browser/resources/shared/js/local_strings.js (renamed from chrome/browser/resources/local_strings.js)2
-rw-r--r--chrome/browser/resources/shared/js/parse_html_subset.js79
-rw-r--r--chrome/browser/resources/shared/js/parse_html_subset_test.html85
-rw-r--r--chrome/common/common_resources.grd2
14 files changed, 680 insertions, 580 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 9bf2c33..43e46c6 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -48,7 +48,7 @@ without changes to the corresponding grd file. ptrrraer -->
<include name="IDR_CALENDAR_APP_MANIFEST" file="resources\calendar_app\manifest.json" type="BINDATA" />
<include name="IDR_DOCS_APP_MANIFEST" file="resources\docs_app\manifest.json" type="BINDATA" />
<include name="IDR_DOWNLOADS_HTML" file="resources\downloads.html" flattenhtml="true" type="BINDATA" />
- <include name="IDR_LOCAL_STRINGS_JS" file="resources\local_strings.js" type="BINDATA" />
+ <include name="IDR_LOCAL_STRINGS_JS" file="resources\shared\js\local_strings.js" type="BINDATA" />
<include name="IDR_DOM_UI_CSS" file="resources\dom_ui.css" flattenhtml="true" type="BINDATA" />
<include name="IDR_EXTENSIONS_UI_HTML" file="resources\extensions_ui.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_EXTENSION_DEFAULT_ICON" file="resources\extension_default_icon.png" type="BINDATA" />
diff --git a/chrome/browser/resources/downloads.html b/chrome/browser/resources/downloads.html
index 0b31aac..449fb19 100644
--- a/chrome/browser/resources/downloads.html
+++ b/chrome/browser/resources/downloads.html
@@ -191,7 +191,7 @@ html[dir=rtl] .name {
}
</style>
-<script src="local_strings.js"></script>
+<script src="shared/js/local_strings.js"></script>
<script>
///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/resources/filebrowse.html b/chrome/browser/resources/filebrowse.html
index 53adc98..61ec0ea 100644
--- a/chrome/browser/resources/filebrowse.html
+++ b/chrome/browser/resources/filebrowse.html
@@ -559,7 +559,7 @@ div.fullcontainer {
}
</style>
-<script src="local_strings.js"></script>
+<script src="shared/js/local_strings.js"></script>
<script>
diff --git a/chrome/browser/resources/history.html b/chrome/browser/resources/history.html
index 69fbca5..ae78ff2 100644
--- a/chrome/browser/resources/history.html
+++ b/chrome/browser/resources/history.html
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<title i18n-content="title"></title>
<link rel="icon" href="../../app/theme/history_favicon.png">
-<script src="local_strings.js"></script>
+<script src="shared/js/local_strings.js"></script>
<script>
///////////////////////////////////////////////////////////////////////////////
// Globals:
diff --git a/chrome/browser/resources/mediaplayer.html b/chrome/browser/resources/mediaplayer.html
index ba14bde..51483fa 100644
--- a/chrome/browser/resources/mediaplayer.html
+++ b/chrome/browser/resources/mediaplayer.html
@@ -259,7 +259,7 @@ body {
}
</style>
-<script src='local_strings.js'></script>
+<script src="shared/js/local_strings.js"></script>
<script>
function $(o) {
@@ -326,7 +326,7 @@ function onMediaComplete() {
mediaElement.removeEventListener("play", onMediaPlay, true);
mediaElement.removeEventListener("pause", onMediaPause, true);
currentItem ++;
-
+
if (currentItem >= currentPlaylist.length) {
currentItem = -1;
return;
@@ -346,7 +346,7 @@ function onMediaPause() {
var pausebutton = $('pausebutton');
var playbutton = $('playbutton');
playbutton.style.display = 'block';
- pausebutton.style.display = 'none';
+ pausebutton.style.display = 'none';
};
function setupMediaEvents(element) {
@@ -444,7 +444,7 @@ function setupPlaybackControls() {
playbutton.className = controlsclass + ' playbutton';
playbutton.onclick = playPauseButtonClick;
element.appendChild(playbutton);
-
+
var pausebutton = document.createElement('div');
pausebutton.id = 'pausebutton';
@@ -480,7 +480,7 @@ function setupPlaybackControls() {
var sliderback = document.createElement('div');
sliderback.className = 'sliderback';
element.appendChild(sliderback);
-
+
var loaded = document.createElement('div');
loaded.id = 'sliderloaded';
loaded.className = 'sliderloaded';
diff --git a/chrome/browser/resources/new_new_tab.html b/chrome/browser/resources/new_new_tab.html
index 019701e..cfebba8 100644
--- a/chrome/browser/resources/new_new_tab.html
+++ b/chrome/browser/resources/new_new_tab.html
@@ -294,7 +294,9 @@ function updateSimpleSection(id, section) {
<div class="window-menu" id="window-tooltip"></div>
</body>
-<script src="i18n_template.js"></script>
-<script src="local_strings.js"></script>
+<script src="shared/js/i18n_template.js"></script>
+<script src="shared/js/local_strings.js"></script>
+<script src="shared/js/parse_html_subset.js"></script>
<script src="new_new_tab.js"></script>
+<script src="ntp/most_visited.js"></script>
</html>
diff --git a/chrome/browser/resources/new_new_tab.js b/chrome/browser/resources/new_new_tab.js
index 0add83b..64c7e1a 100644
--- a/chrome/browser/resources/new_new_tab.js
+++ b/chrome/browser/resources/new_new_tab.js
@@ -1,3 +1,6 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
// Helpers
@@ -47,32 +50,6 @@ function bind(fn, selfObj, var_args) {
const IS_MAC = /$Mac/.test(navigator.platform);
var loading = true;
-var mostVisitedData = [];
-var gotMostVisited = false;
-
-function mostVisitedPages(data, firstRun) {
- logEvent('received most visited pages');
-
- // We append the class name with the "filler" so that we can style fillers
- // differently.
- var maxItems = 8;
- data.length = Math.min(maxItems, data.length);
- var len = data.length;
- for (var i = len; i < maxItems; i++) {
- data[i] = {filler: true};
- }
-
- mostVisitedData = data;
- renderMostVisited(data);
-
- gotMostVisited = true;
- onDataLoaded();
-
- // Only show the first run notification if first run.
- if (firstRun) {
- showFirstRunNotification();
- }
-}
function getAppsCallback(data) {
var appsSection = $('apps-section');
@@ -239,12 +216,6 @@ function saveShownSections() {
chrome.send('setShownSections', [String(shownSections)]);
}
-function getThumbnailClassName(data) {
- return 'thumbnail-container' +
- (data.pinned ? ' pinned' : '') +
- (data.filler ? ' filler' : '');
-}
-
function url(s) {
// http://www.w3.org/TR/css3-values/#uris
// Parentheses, commas, whitespace characters, single quotes (') and double
@@ -259,50 +230,6 @@ function url(s) {
return 'url("' + s2 + '")';
}
-function renderMostVisited(data) {
- var parent = $('most-visited');
- var children = parent.children;
- for (var i = 0; i < data.length; i++) {
- var d = data[i];
- var t = children[i];
-
- // If we have a filler continue
- var oldClassName = t.className;
- var newClassName = getThumbnailClassName(d);
- if (oldClassName != newClassName) {
- t.className = newClassName;
- }
-
- // No need to continue if this is a filler.
- if (newClassName == 'thumbnail-container filler') {
- // Make sure the user cannot tab to the filler.
- t.tabIndex = -1;
- continue;
- }
- // Allow focus.
- t.tabIndex = 1;
-
- t.href = d.url;
- t.querySelector('.pin').title = localStrings.getString(d.pinned ?
- 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
- t.querySelector('.remove').title =
- localStrings.getString('removethumbnailtooltip');
-
- // There was some concern that a malformed malicious URL could cause an XSS
- // attack but setting style.backgroundImage = 'url(javascript:...)' does
- // not execute the JavaScript in WebKit.
-
- var thumbnailUrl = d.thumbnailUrl || 'chrome://thumb/' + d.url;
- t.querySelector('.thumbnail-wrapper').style.backgroundImage =
- url(thumbnailUrl);
- var titleDiv = t.querySelector('.title > div');
- titleDiv.xtitle = titleDiv.textContent = d.title;
- var faviconUrl = d.faviconUrl || 'chrome://favicon/' + d.url;
- titleDiv.style.backgroundImage = url(faviconUrl);
- titleDiv.dir = d.direction;
- }
-}
-
/**
* Calls chrome.send with a callback and restores the original afterwards.
*/
@@ -387,194 +314,6 @@ function hideSection(section) {
}
}
-var mostVisited = {
- addPinnedUrl_: function(data, index) {
- chrome.send('addPinnedURL', [data.url, data.title, data.faviconUrl || '',
- data.thumbnailUrl || '', String(index)]);
- },
- getItem: function(el) {
- return findAncestorByClass(el, 'thumbnail-container');
- },
-
- getHref: function(el) {
- return el.href;
- },
-
- togglePinned: function(el) {
- var index = this.getThumbnailIndex(el);
- var data = mostVisitedData[index];
- data.pinned = !data.pinned;
- if (data.pinned) {
- this.addPinnedUrl_(data, index);
- } else {
- chrome.send('removePinnedURL', [data.url]);
- }
- this.updatePinnedDom_(el, data.pinned);
- },
-
- updatePinnedDom_: function(el, pinned) {
- el.querySelector('.pin').title = localStrings.getString(pinned ?
- 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
- if (pinned) {
- addClass(el, 'pinned');
- } else {
- removeClass(el, 'pinned');
- }
- },
-
- getThumbnailIndex: function(el) {
- var nodes = el.parentNode.querySelectorAll('.thumbnail-container');
- return Array.prototype.indexOf.call(nodes, el);
- },
-
- swapPosition: function(source, destination) {
- var nodes = source.parentNode.querySelectorAll('.thumbnail-container');
- var sourceIndex = this.getThumbnailIndex(source);
- var destinationIndex = this.getThumbnailIndex(destination);
- swapDomNodes(source, destination);
-
- var sourceData = mostVisitedData[sourceIndex];
- this.addPinnedUrl_(sourceData, destinationIndex);
- sourceData.pinned = true;
- this.updatePinnedDom_(source, true);
-
- var destinationData = mostVisitedData[destinationIndex];
- // Only update the destination if it was pinned before.
- if (destinationData.pinned) {
- this.addPinnedUrl_(destinationData, sourceIndex);
- }
- mostVisitedData[destinationIndex] = sourceData;
- mostVisitedData[sourceIndex] = destinationData;
- },
-
- blacklist: function(el) {
- var self = this;
- var url = this.getHref(el);
- chrome.send('blacklistURLFromMostVisited', [url]);
-
- addClass(el, 'hide');
-
- // Find the old item.
- var oldUrls = {};
- var oldIndex = -1;
- var oldItem;
- for (var i = 0; i < mostVisitedData.length; i++) {
- if (mostVisitedData[i].url == url) {
- oldItem = mostVisitedData[i];
- oldIndex = i;
- }
- oldUrls[mostVisitedData[i].url] = true;
- }
-
- // Send 'getMostVisitedPages' with a callback since we want to find the new
- // page and add that in the place of the removed page.
- chromeSend('getMostVisited', [], 'mostVisitedPages', function(data) {
- // Find new item.
- var newItem;
- for (var i = 0; i < data.length; i++) {
- if (!(data[i].url in oldUrls)) {
- newItem = data[i];
- break;
- }
- }
-
- if (!newItem) {
- // If no other page is available to replace the blacklisted item,
- // we need to reorder items s.t. all filler items are in the rightmost
- // indices.
- mostVisitedPages(data);
-
- // Replace old item with new item in the mostVisitedData array.
- } else if (oldIndex != -1) {
- mostVisitedData.splice(oldIndex, 1, newItem);
- mostVisitedPages(mostVisitedData);
- addClass(el, 'fade-in');
- }
-
- // We wrap the title in a <span class=blacklisted-title>. We pass an empty
- // string to the notifier function and use DOM to insert the real string.
- var actionText = localStrings.getString('undothumbnailremove');
-
- // Show notification and add undo callback function.
- var wasPinned = oldItem.pinned;
- showNotification('', actionText, function() {
- self.removeFromBlackList(url);
- if (wasPinned) {
- self.addPinnedUrl_(oldItem, oldIndex);
- }
- chrome.send('getMostVisited');
- });
-
- // Now change the DOM.
- var removeText = localStrings.getString('thumbnailremovednotification');
- var notifySpan = document.querySelector('#notification > span');
- notifySpan.textContent = removeText;
-
- // Focus the undo link.
- var undoLink = document.querySelector(
- '#notification > .link > [tabindex]');
- undoLink.focus();
- });
- },
-
- removeFromBlackList: function(url) {
- chrome.send('removeURLsFromMostVisitedBlacklist', [url]);
- },
-
- clearAllBlacklisted: function() {
- chrome.send('clearMostVisitedURLsBlacklist', []);
- hideNotification();
- },
-
- updateDisplayMode: function() {
- if (!this.dirty_) {
- return;
- }
- updateSimpleSection('most-visited-section', Section.THUMB);
- },
-
- dirty_: false,
-
- invalidate: function() {
- this.dirty_ = true;
- },
-
- layout: function() {
- if (!this.dirty_) {
- return;
- }
- var d0 = Date.now();
-
- var mostVisitedElement = $('most-visited');
- var thumbnails = mostVisitedElement.children;
- var hidden = !(shownSections & Section.THUMB);
-
-
- // We set overflow to hidden so that the most visited element does not
- // "leak" when we hide and show it.
- if (hidden) {
- mostVisitedElement.style.overflow = 'hidden';
- }
-
- applyMostVisitedRects();
-
- // Only set overflow to visible if the element is shown.
- if (!hidden) {
- afterTransition(function() {
- mostVisitedElement.style.overflow = '';
- });
- }
-
- this.dirty_ = false;
-
- logEvent('mostVisited.layout: ' + (Date.now() - d0));
- },
-
- getRectByIndex: function(index) {
- return getMostVisitedLayoutRects()[index];
- }
-};
-
// Recently closed
function layoutRecentlyClosed() {
@@ -854,7 +593,6 @@ function showFirstRunNotification() {
addClass(notification, 'first-run');
}
-
/**
* This handles the option menu.
* @param {Element} button The button element.
@@ -1053,25 +791,6 @@ optionMenu.commands = {
}
};
-$('most-visited').addEventListener('click', function(e) {
- var target = e.target;
- if (hasClass(target, 'pin')) {
- mostVisited.togglePinned(mostVisited.getItem(target));
- e.preventDefault();
- } else if (hasClass(target, 'remove')) {
- mostVisited.blacklist(mostVisited.getItem(target));
- e.preventDefault();
- }
-});
-
-// Allow blacklisting most visited site using the keyboard.
-$('most-visited').addEventListener('keydown', function(e) {
- if (!IS_MAC && e.keyCode == 46 || // Del
- IS_MAC && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
- mostVisited.blacklist(e.target);
- }
-});
-
$('main').addEventListener('click', function(e) {
if (e.target.tagName == 'H2') {
var p = e.target.parentNode;
@@ -1265,14 +984,6 @@ document.addEventListener('DOMContentLoaded',
// Set up links and text-decoration for promotional message.
document.addEventListener('DOMContentLoaded', setUpPromoMessage);
-// Work around for http://crbug.com/25329
-function ensureSmallGridCorrect() {
- if (wasSmallGrid != useSmallGrid()) {
- applyMostVisitedRects();
- }
-}
-document.addEventListener('DOMContentLoaded', ensureSmallGridCorrect);
-
/**
* The sync code is not yet built by default on all platforms so we have to
* make sure we don't send the initial sync message to the backend unless the
@@ -1326,279 +1037,6 @@ document.addEventListener('mouseover', function(e) {
}
});
-// DnD
-
-var dnd = {
- currentOverItem_: null,
- get currentOverItem() {
- return this.currentOverItem_;
- },
- set currentOverItem(item) {
- var style;
- if (item != this.currentOverItem_) {
- if (this.currentOverItem_) {
- style = this.currentOverItem_.firstElementChild.style;
- style.left = style.top = '';
- }
- this.currentOverItem_ = item;
-
- if (item) {
- // Make the drag over item move 15px towards the source. The movement is
- // done by only moving the edit-mode-border (as in the mocks) and it is
- // done with relative positioning so that the movement does not change
- // the drop target.
- var dragIndex = mostVisited.getThumbnailIndex(this.dragItem);
- var overIndex = mostVisited.getThumbnailIndex(item);
- if (dragIndex == -1 || overIndex == -1) {
- return;
- }
-
- var dragRect = mostVisited.getRectByIndex(dragIndex);
- var overRect = mostVisited.getRectByIndex(overIndex);
-
- var x = dragRect.left - overRect.left;
- var y = dragRect.top - overRect.top;
- var z = Math.sqrt(x * x + y * y);
- var z2 = 15;
- var x2 = x * z2 / z;
- var y2 = y * z2 / z;
-
- style = this.currentOverItem_.firstElementChild.style;
- style.left = x2 + 'px';
- style.top = y2 + 'px';
- }
- }
- },
- dragItem: null,
- startX: 0,
- startY: 0,
- startScreenX: 0,
- startScreenY: 0,
- dragEndTimer: null,
-
- handleDragStart: function(e) {
- var thumbnail = mostVisited.getItem(e.target);
- if (thumbnail) {
- // Don't set data since HTML5 does not allow setting the name for
- // url-list. Instead, we just rely on the dragging of link behavior.
- this.dragItem = thumbnail;
- addClass(this.dragItem, 'dragging');
- this.dragItem.style.zIndex = 2;
- e.dataTransfer.effectAllowed = 'copyLinkMove';
- }
- },
-
- handleDragEnter: function(e) {
- if (this.canDropOnElement(this.currentOverItem)) {
- e.preventDefault();
- }
- },
-
- handleDragOver: function(e) {
- var item = mostVisited.getItem(e.target);
- this.currentOverItem = item;
- if (this.canDropOnElement(item)) {
- e.preventDefault();
- e.dataTransfer.dropEffect = 'move';
- }
- },
-
- handleDragLeave: function(e) {
- var item = mostVisited.getItem(e.target);
- if (item) {
- e.preventDefault();
- }
-
- this.currentOverItem = null;
- },
-
- handleDrop: function(e) {
- var dropTarget = mostVisited.getItem(e.target);
- if (this.canDropOnElement(dropTarget)) {
- dropTarget.style.zIndex = 1;
- mostVisited.swapPosition(this.dragItem, dropTarget);
- // The timeout below is to allow WebKit to see that we turned off
- // pointer-event before moving the thumbnails so that we can get out of
- // hover mode.
- window.setTimeout(function() {
- mostVisited.invalidate();
- mostVisited.layout();
- }, 10);
- e.preventDefault();
- if (this.dragEndTimer) {
- window.clearTimeout(this.dragEndTimer);
- this.dragEndTimer = null;
- }
- afterTransition(function() {
- dropTarget.style.zIndex = '';
- });
- }
- },
-
- handleDragEnd: function(e) {
- var dragItem = this.dragItem;
- if (dragItem) {
- dragItem.style.pointerEvents = '';
- removeClass(dragItem, 'dragging');
-
- afterTransition(function() {
- // Delay resetting zIndex to let the animation finish.
- dragItem.style.zIndex = '';
- // Same for overflow.
- dragItem.parentNode.style.overflow = '';
- });
-
- mostVisited.invalidate();
- mostVisited.layout();
- this.dragItem = null;
- }
- },
-
- handleDrag: function(e) {
- // Moves the drag item making sure that it is not displayed outside the
- // browser viewport.
- var item = mostVisited.getItem(e.target);
- var rect = document.querySelector('#most-visited').getBoundingClientRect();
- item.style.pointerEvents = 'none';
-
- var x = this.startX + e.screenX - this.startScreenX;
- var y = this.startY + e.screenY - this.startScreenY;
-
- // The position of the item is relative to #most-visited so we need to
- // subtract that when calculating the allowed position.
- x = Math.max(x, -rect.left);
- x = Math.min(x, document.body.clientWidth - rect.left - item.offsetWidth -
- 2);
- // The shadow is 2px
- y = Math.max(-rect.top, y);
- y = Math.min(y, document.body.clientHeight - rect.top - item.offsetHeight -
- 2);
-
- // Override right in case of RTL.
- item.style.right = 'auto';
- item.style.left = x + 'px';
- item.style.top = y + 'px';
- item.style.zIndex = 2;
- },
-
- // We listen to mousedown to get the relative position of the cursor for dnd.
- handleMouseDown: function(e) {
- var item = mostVisited.getItem(e.target);
- if (item) {
- this.startX = item.offsetLeft;
- this.startY = item.offsetTop;
- this.startScreenX = e.screenX;
- this.startScreenY = e.screenY;
-
- // We don't want to focus the item on mousedown. However, to prevent focus
- // one has to call preventDefault but this also prevents the drag and drop
- // (sigh) so we only prevent it when the user is not doing a left mouse
- // button drag.
- if (e.button != 0) // LEFT
- e.preventDefault();
- }
- },
-
- canDropOnElement: function(el) {
- return this.dragItem && el && hasClass(el, 'thumbnail-container') &&
- !hasClass(el, 'filler');
- },
-
- init: function() {
- var el = $('most-visited');
- el.addEventListener('dragstart', bind(this.handleDragStart, this));
- el.addEventListener('dragenter', bind(this.handleDragEnter, this));
- el.addEventListener('dragover', bind(this.handleDragOver, this));
- el.addEventListener('dragleave', bind(this.handleDragLeave, this));
- el.addEventListener('drop', bind(this.handleDrop, this));
- el.addEventListener('dragend', bind(this.handleDragEnd, this));
- el.addEventListener('drag', bind(this.handleDrag, this));
- el.addEventListener('mousedown', bind(this.handleMouseDown, this));
- }
-};
-
-dnd.init();
-
-/**
- * Whitelist of tag names allowed in parseHtmlSubset.
- * @type {[string]}
- */
-var allowedTags = ['A', 'B', 'STRONG'];
-
-/**
- * Parse a very small subset of HTML.
- * @param {string} s The string to parse.
- * @throws {Error} In case of non supported markup.
- * @return {DocumentFragment} A document fragment containing the DOM tree.
- */
-var allowedAttributes = {
- 'href': function(node, value) {
- // Only allow a[href] starting with http:// and https://
- return node.tagName == 'A' && (value.indexOf('http://') == 0 ||
- value.indexOf('https://') == 0);
- },
- 'target': function(node, value) {
- // Allow a[target] but reset the value to "".
- if (node.tagName != 'A')
- return false;
- node.setAttribute('target', '');
- return true;
- }
-}
-
-/**
- * Parse a very small subset of HTML. This ensures that insecure HTML /
- * javascript cannot be injected into the new tab page.
- * @param {string} s The string to parse.
- * @throws {Error} In case of non supported markup.
- * @return {DocumentFragment} A document fragment containing the DOM tree.
- */
-function parseHtmlSubset(s) {
- function walk(n, f) {
- f(n);
- for (var i = 0; i < n.childNodes.length; i++) {
- walk(n.childNodes[i], f);
- }
- }
-
- function assertElement(node) {
- if (allowedTags.indexOf(node.tagName) == -1)
- throw Error(node.tagName + ' is not supported');
- }
-
- function assertAttribute(attrNode, node) {
- var n = attrNode.nodeName;
- var v = attrNode.nodeValue;
- if (!allowedAttributes.hasOwnProperty(n) || !allowedAttributes[n](node, v))
- throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported');
- }
-
- var r = document.createRange();
- r.selectNode(document.body);
- // This does not execute any scripts.
- var df = r.createContextualFragment(s);
- walk(df, function(node) {
- switch (node.nodeType) {
- case Node.ELEMENT_NODE:
- assertElement(node);
- var attrs = node.attributes;
- for (var i = 0; i < attrs.length; i++) {
- assertAttribute(attrs[i], node);
- }
- break;
-
- case Node.COMMENT_NODE:
- case Node.DOCUMENT_FRAGMENT_NODE:
- case Node.TEXT_NODE:
- break;
-
- default:
- throw Error('Node type ' + node.nodeType + ' is not supported');
- }
- });
- return df;
-}
-
/**
* Makes links and buttons support a different underline color.
* @param {Node} node The node to search for links and buttons in.
diff --git a/chrome/browser/resources/ntp/most_visited.js b/chrome/browser/resources/ntp/most_visited.js
new file mode 100644
index 0000000..9a19c0d
--- /dev/null
+++ b/chrome/browser/resources/ntp/most_visited.js
@@ -0,0 +1,492 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var mostVisitedData = [];
+var gotMostVisited = false;
+
+function mostVisitedPages(data, firstRun) {
+ logEvent('received most visited pages');
+
+ // We append the class name with the "filler" so that we can style fillers
+ // differently.
+ var maxItems = 8;
+ data.length = Math.min(maxItems, data.length);
+ var len = data.length;
+ for (var i = len; i < maxItems; i++) {
+ data[i] = {filler: true};
+ }
+
+ mostVisitedData = data;
+ renderMostVisited(data);
+
+ gotMostVisited = true;
+ onDataLoaded();
+
+ // Only show the first run notification if first run.
+ if (firstRun) {
+ showFirstRunNotification();
+ }
+}
+function getThumbnailClassName(data) {
+ return 'thumbnail-container' +
+ (data.pinned ? ' pinned' : '') +
+ (data.filler ? ' filler' : '');
+}
+
+function renderMostVisited(data) {
+ var parent = $('most-visited');
+ var children = parent.children;
+ for (var i = 0; i < data.length; i++) {
+ var d = data[i];
+ var t = children[i];
+
+ // If we have a filler continue
+ var oldClassName = t.className;
+ var newClassName = getThumbnailClassName(d);
+ if (oldClassName != newClassName) {
+ t.className = newClassName;
+ }
+
+ // No need to continue if this is a filler.
+ if (newClassName == 'thumbnail-container filler') {
+ // Make sure the user cannot tab to the filler.
+ t.tabIndex = -1;
+ continue;
+ }
+ // Allow focus.
+ t.tabIndex = 1;
+
+ t.href = d.url;
+ t.querySelector('.pin').title = localStrings.getString(d.pinned ?
+ 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
+ t.querySelector('.remove').title =
+ localStrings.getString('removethumbnailtooltip');
+
+ // There was some concern that a malformed malicious URL could cause an XSS
+ // attack but setting style.backgroundImage = 'url(javascript:...)' does
+ // not execute the JavaScript in WebKit.
+
+ var thumbnailUrl = d.thumbnailUrl || 'chrome://thumb/' + d.url;
+ t.querySelector('.thumbnail-wrapper').style.backgroundImage =
+ url(thumbnailUrl);
+ var titleDiv = t.querySelector('.title > div');
+ titleDiv.xtitle = titleDiv.textContent = d.title;
+ var faviconUrl = d.faviconUrl || 'chrome://favicon/' + d.url;
+ titleDiv.style.backgroundImage = url(faviconUrl);
+ titleDiv.dir = d.direction;
+ }
+}
+
+var mostVisited = {
+ addPinnedUrl_: function(data, index) {
+ chrome.send('addPinnedURL', [data.url, data.title, data.faviconUrl || '',
+ data.thumbnailUrl || '', String(index)]);
+ },
+ getItem: function(el) {
+ return findAncestorByClass(el, 'thumbnail-container');
+ },
+
+ getHref: function(el) {
+ return el.href;
+ },
+
+ togglePinned: function(el) {
+ var index = this.getThumbnailIndex(el);
+ var data = mostVisitedData[index];
+ data.pinned = !data.pinned;
+ if (data.pinned) {
+ this.addPinnedUrl_(data, index);
+ } else {
+ chrome.send('removePinnedURL', [data.url]);
+ }
+ this.updatePinnedDom_(el, data.pinned);
+ },
+
+ updatePinnedDom_: function(el, pinned) {
+ el.querySelector('.pin').title = localStrings.getString(pinned ?
+ 'unpinthumbnailtooltip' : 'pinthumbnailtooltip');
+ if (pinned) {
+ addClass(el, 'pinned');
+ } else {
+ removeClass(el, 'pinned');
+ }
+ },
+
+ getThumbnailIndex: function(el) {
+ var nodes = el.parentNode.querySelectorAll('.thumbnail-container');
+ return Array.prototype.indexOf.call(nodes, el);
+ },
+
+ swapPosition: function(source, destination) {
+ var nodes = source.parentNode.querySelectorAll('.thumbnail-container');
+ var sourceIndex = this.getThumbnailIndex(source);
+ var destinationIndex = this.getThumbnailIndex(destination);
+ swapDomNodes(source, destination);
+
+ var sourceData = mostVisitedData[sourceIndex];
+ this.addPinnedUrl_(sourceData, destinationIndex);
+ sourceData.pinned = true;
+ this.updatePinnedDom_(source, true);
+
+ var destinationData = mostVisitedData[destinationIndex];
+ // Only update the destination if it was pinned before.
+ if (destinationData.pinned) {
+ this.addPinnedUrl_(destinationData, sourceIndex);
+ }
+ mostVisitedData[destinationIndex] = sourceData;
+ mostVisitedData[sourceIndex] = destinationData;
+ },
+
+ blacklist: function(el) {
+ var self = this;
+ var url = this.getHref(el);
+ chrome.send('blacklistURLFromMostVisited', [url]);
+
+ addClass(el, 'hide');
+
+ // Find the old item.
+ var oldUrls = {};
+ var oldIndex = -1;
+ var oldItem;
+ for (var i = 0; i < mostVisitedData.length; i++) {
+ if (mostVisitedData[i].url == url) {
+ oldItem = mostVisitedData[i];
+ oldIndex = i;
+ }
+ oldUrls[mostVisitedData[i].url] = true;
+ }
+
+ // Send 'getMostVisitedPages' with a callback since we want to find the new
+ // page and add that in the place of the removed page.
+ chromeSend('getMostVisited', [], 'mostVisitedPages', function(data) {
+ // Find new item.
+ var newItem;
+ for (var i = 0; i < data.length; i++) {
+ if (!(data[i].url in oldUrls)) {
+ newItem = data[i];
+ break;
+ }
+ }
+
+ if (!newItem) {
+ // If no other page is available to replace the blacklisted item,
+ // we need to reorder items s.t. all filler items are in the rightmost
+ // indices.
+ mostVisitedPages(data);
+
+ // Replace old item with new item in the mostVisitedData array.
+ } else if (oldIndex != -1) {
+ mostVisitedData.splice(oldIndex, 1, newItem);
+ mostVisitedPages(mostVisitedData);
+ addClass(el, 'fade-in');
+ }
+
+ // We wrap the title in a <span class=blacklisted-title>. We pass an empty
+ // string to the notifier function and use DOM to insert the real string.
+ var actionText = localStrings.getString('undothumbnailremove');
+
+ // Show notification and add undo callback function.
+ var wasPinned = oldItem.pinned;
+ showNotification('', actionText, function() {
+ self.removeFromBlackList(url);
+ if (wasPinned) {
+ self.addPinnedUrl_(oldItem, oldIndex);
+ }
+ chrome.send('getMostVisited');
+ });
+
+ // Now change the DOM.
+ var removeText = localStrings.getString('thumbnailremovednotification');
+ var notifySpan = document.querySelector('#notification > span');
+ notifySpan.textContent = removeText;
+
+ // Focus the undo link.
+ var undoLink = document.querySelector(
+ '#notification > .link > [tabindex]');
+ undoLink.focus();
+ });
+ },
+
+ removeFromBlackList: function(url) {
+ chrome.send('removeURLsFromMostVisitedBlacklist', [url]);
+ },
+
+ clearAllBlacklisted: function() {
+ chrome.send('clearMostVisitedURLsBlacklist', []);
+ hideNotification();
+ },
+
+ updateDisplayMode: function() {
+ if (!this.dirty_) {
+ return;
+ }
+ updateSimpleSection('most-visited-section', Section.THUMB);
+ },
+
+ dirty_: false,
+
+ invalidate: function() {
+ this.dirty_ = true;
+ },
+
+ layout: function() {
+ if (!this.dirty_) {
+ return;
+ }
+ var d0 = Date.now();
+
+ var mostVisitedElement = $('most-visited');
+ var thumbnails = mostVisitedElement.children;
+ var hidden = !(shownSections & Section.THUMB);
+
+
+ // We set overflow to hidden so that the most visited element does not
+ // "leak" when we hide and show it.
+ if (hidden) {
+ mostVisitedElement.style.overflow = 'hidden';
+ }
+
+ applyMostVisitedRects();
+
+ // Only set overflow to visible if the element is shown.
+ if (!hidden) {
+ afterTransition(function() {
+ mostVisitedElement.style.overflow = '';
+ });
+ }
+
+ this.dirty_ = false;
+
+ logEvent('mostVisited.layout: ' + (Date.now() - d0));
+ },
+
+ getRectByIndex: function(index) {
+ return getMostVisitedLayoutRects()[index];
+ }
+};
+
+$('most-visited').addEventListener('click', function(e) {
+ var target = e.target;
+ if (hasClass(target, 'pin')) {
+ mostVisited.togglePinned(mostVisited.getItem(target));
+ e.preventDefault();
+ } else if (hasClass(target, 'remove')) {
+ mostVisited.blacklist(mostVisited.getItem(target));
+ e.preventDefault();
+ }
+});
+
+// Allow blacklisting most visited site using the keyboard.
+$('most-visited').addEventListener('keydown', function(e) {
+ if (!IS_MAC && e.keyCode == 46 || // Del
+ IS_MAC && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
+ mostVisited.blacklist(e.target);
+ }
+});
+
+window.addEventListener('load', onDataLoaded);
+
+window.addEventListener('resize', handleWindowResize);
+
+// Work around for http://crbug.com/25329
+function ensureSmallGridCorrect() {
+ if (wasSmallGrid != useSmallGrid()) {
+ applyMostVisitedRects();
+ }
+}
+document.addEventListener('DOMContentLoaded', ensureSmallGridCorrect);
+
+// DnD
+
+var dnd = {
+ currentOverItem_: null,
+ get currentOverItem() {
+ return this.currentOverItem_;
+ },
+ set currentOverItem(item) {
+ var style;
+ if (item != this.currentOverItem_) {
+ if (this.currentOverItem_) {
+ style = this.currentOverItem_.firstElementChild.style;
+ style.left = style.top = '';
+ }
+ this.currentOverItem_ = item;
+
+ if (item) {
+ // Make the drag over item move 15px towards the source. The movement is
+ // done by only moving the edit-mode-border (as in the mocks) and it is
+ // done with relative positioning so that the movement does not change
+ // the drop target.
+ var dragIndex = mostVisited.getThumbnailIndex(this.dragItem);
+ var overIndex = mostVisited.getThumbnailIndex(item);
+ if (dragIndex == -1 || overIndex == -1) {
+ return;
+ }
+
+ var dragRect = mostVisited.getRectByIndex(dragIndex);
+ var overRect = mostVisited.getRectByIndex(overIndex);
+
+ var x = dragRect.left - overRect.left;
+ var y = dragRect.top - overRect.top;
+ var z = Math.sqrt(x * x + y * y);
+ var z2 = 15;
+ var x2 = x * z2 / z;
+ var y2 = y * z2 / z;
+
+ style = this.currentOverItem_.firstElementChild.style;
+ style.left = x2 + 'px';
+ style.top = y2 + 'px';
+ }
+ }
+ },
+ dragItem: null,
+ startX: 0,
+ startY: 0,
+ startScreenX: 0,
+ startScreenY: 0,
+ dragEndTimer: null,
+
+ handleDragStart: function(e) {
+ var thumbnail = mostVisited.getItem(e.target);
+ if (thumbnail) {
+ // Don't set data since HTML5 does not allow setting the name for
+ // url-list. Instead, we just rely on the dragging of link behavior.
+ this.dragItem = thumbnail;
+ addClass(this.dragItem, 'dragging');
+ this.dragItem.style.zIndex = 2;
+ e.dataTransfer.effectAllowed = 'copyLinkMove';
+ }
+ },
+
+ handleDragEnter: function(e) {
+ if (this.canDropOnElement(this.currentOverItem)) {
+ e.preventDefault();
+ }
+ },
+
+ handleDragOver: function(e) {
+ var item = mostVisited.getItem(e.target);
+ this.currentOverItem = item;
+ if (this.canDropOnElement(item)) {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = 'move';
+ }
+ },
+
+ handleDragLeave: function(e) {
+ var item = mostVisited.getItem(e.target);
+ if (item) {
+ e.preventDefault();
+ }
+
+ this.currentOverItem = null;
+ },
+
+ handleDrop: function(e) {
+ var dropTarget = mostVisited.getItem(e.target);
+ if (this.canDropOnElement(dropTarget)) {
+ dropTarget.style.zIndex = 1;
+ mostVisited.swapPosition(this.dragItem, dropTarget);
+ // The timeout below is to allow WebKit to see that we turned off
+ // pointer-event before moving the thumbnails so that we can get out of
+ // hover mode.
+ window.setTimeout(function() {
+ mostVisited.invalidate();
+ mostVisited.layout();
+ }, 10);
+ e.preventDefault();
+ if (this.dragEndTimer) {
+ window.clearTimeout(this.dragEndTimer);
+ this.dragEndTimer = null;
+ }
+ afterTransition(function() {
+ dropTarget.style.zIndex = '';
+ });
+ }
+ },
+
+ handleDragEnd: function(e) {
+ var dragItem = this.dragItem;
+ if (dragItem) {
+ dragItem.style.pointerEvents = '';
+ removeClass(dragItem, 'dragging');
+
+ afterTransition(function() {
+ // Delay resetting zIndex to let the animation finish.
+ dragItem.style.zIndex = '';
+ // Same for overflow.
+ dragItem.parentNode.style.overflow = '';
+ });
+
+ mostVisited.invalidate();
+ mostVisited.layout();
+ this.dragItem = null;
+ }
+ },
+
+ handleDrag: function(e) {
+ // Moves the drag item making sure that it is not displayed outside the
+ // browser viewport.
+ var item = mostVisited.getItem(e.target);
+ var rect = document.querySelector('#most-visited').getBoundingClientRect();
+ item.style.pointerEvents = 'none';
+
+ var x = this.startX + e.screenX - this.startScreenX;
+ var y = this.startY + e.screenY - this.startScreenY;
+
+ // The position of the item is relative to #most-visited so we need to
+ // subtract that when calculating the allowed position.
+ x = Math.max(x, -rect.left);
+ x = Math.min(x, document.body.clientWidth - rect.left - item.offsetWidth -
+ 2);
+ // The shadow is 2px
+ y = Math.max(-rect.top, y);
+ y = Math.min(y, document.body.clientHeight - rect.top - item.offsetHeight -
+ 2);
+
+ // Override right in case of RTL.
+ item.style.right = 'auto';
+ item.style.left = x + 'px';
+ item.style.top = y + 'px';
+ item.style.zIndex = 2;
+ },
+
+ // We listen to mousedown to get the relative position of the cursor for dnd.
+ handleMouseDown: function(e) {
+ var item = mostVisited.getItem(e.target);
+ if (item) {
+ this.startX = item.offsetLeft;
+ this.startY = item.offsetTop;
+ this.startScreenX = e.screenX;
+ this.startScreenY = e.screenY;
+
+ // We don't want to focus the item on mousedown. However, to prevent focus
+ // one has to call preventDefault but this also prevents the drag and drop
+ // (sigh) so we only prevent it when the user is not doing a left mouse
+ // button drag.
+ if (e.button != 0) // LEFT
+ e.preventDefault();
+ }
+ },
+
+ canDropOnElement: function(el) {
+ return this.dragItem && el && hasClass(el, 'thumbnail-container') &&
+ !hasClass(el, 'filler');
+ },
+
+ init: function() {
+ var el = $('most-visited');
+ el.addEventListener('dragstart', bind(this.handleDragStart, this));
+ el.addEventListener('dragenter', bind(this.handleDragEnter, this));
+ el.addEventListener('dragover', bind(this.handleDragOver, this));
+ el.addEventListener('dragleave', bind(this.handleDragLeave, this));
+ el.addEventListener('drop', bind(this.handleDrop, this));
+ el.addEventListener('dragend', bind(this.handleDragEnd, this));
+ el.addEventListener('drag', bind(this.handleDrag, this));
+ el.addEventListener('mousedown', bind(this.handleMouseDown, this));
+ }
+};
+
+dnd.init();
+
diff --git a/chrome/browser/resources/playlist.html b/chrome/browser/resources/playlist.html
index 80415f6..3bf0d19 100644
--- a/chrome/browser/resources/playlist.html
+++ b/chrome/browser/resources/playlist.html
@@ -35,11 +35,11 @@
}
.title {
-
+
}
</style>
-<script src='local_strings.js'></script>
+<script src="shared/js/local_strings.js"></script>
<script>
function $(o) {
diff --git a/chrome/browser/resources/i18n_template.js b/chrome/browser/resources/shared/js/i18n_template.js
index 8166ddc..2645bdb 100644
--- a/chrome/browser/resources/i18n_template.js
+++ b/chrome/browser/resources/shared/js/i18n_template.js
@@ -1,3 +1,7 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
/**
* @fileoverview This is a simple template engine inspired by JsTemplates
* optimized for i18n.
diff --git a/chrome/browser/resources/local_strings.js b/chrome/browser/resources/shared/js/local_strings.js
index ecf3f5a..75b9859 100644
--- a/chrome/browser/resources/local_strings.js
+++ b/chrome/browser/resources/shared/js/local_strings.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2009-2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
diff --git a/chrome/browser/resources/shared/js/parse_html_subset.js b/chrome/browser/resources/shared/js/parse_html_subset.js
new file mode 100644
index 0000000..eecf6ee
--- /dev/null
+++ b/chrome/browser/resources/shared/js/parse_html_subset.js
@@ -0,0 +1,79 @@
+/**
+ * Whitelist of tag names allowed in parseHtmlSubset.
+ * @type {[string]}
+ */
+var allowedTags = ['A', 'B', 'STRONG'];
+
+/**
+ * Parse a very small subset of HTML.
+ * @param {string} s The string to parse.
+ * @throws {Error} In case of non supported markup.
+ * @return {DocumentFragment} A document fragment containing the DOM tree.
+ */
+var allowedAttributes = {
+ 'href': function(node, value) {
+ // Only allow a[href] starting with http:// and https://
+ return node.tagName == 'A' && (value.indexOf('http://') == 0 ||
+ value.indexOf('https://') == 0);
+ },
+ 'target': function(node, value) {
+ // Allow a[target] but reset the value to "".
+ if (node.tagName != 'A')
+ return false;
+ node.setAttribute('target', '');
+ return true;
+ }
+}
+
+/**
+ * Parse a very small subset of HTML. This ensures that insecure HTML /
+ * javascript cannot be injected into the new tab page.
+ * @param {string} s The string to parse.
+ * @throws {Error} In case of non supported markup.
+ * @return {DocumentFragment} A document fragment containing the DOM tree.
+ */
+function parseHtmlSubset(s) {
+ function walk(n, f) {
+ f(n);
+ for (var i = 0; i < n.childNodes.length; i++) {
+ walk(n.childNodes[i], f);
+ }
+ }
+
+ function assertElement(node) {
+ if (allowedTags.indexOf(node.tagName) == -1)
+ throw Error(node.tagName + ' is not supported');
+ }
+
+ function assertAttribute(attrNode, node) {
+ var n = attrNode.nodeName;
+ var v = attrNode.nodeValue;
+ if (!allowedAttributes.hasOwnProperty(n) || !allowedAttributes[n](node, v))
+ throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported');
+ }
+
+ var r = document.createRange();
+ r.selectNode(document.body);
+ // This does not execute any scripts.
+ var df = r.createContextualFragment(s);
+ walk(df, function(node) {
+ switch (node.nodeType) {
+ case Node.ELEMENT_NODE:
+ assertElement(node);
+ var attrs = node.attributes;
+ for (var i = 0; i < attrs.length; i++) {
+ assertAttribute(attrs[i], node);
+ }
+ break;
+
+ case Node.COMMENT_NODE:
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ case Node.TEXT_NODE:
+ break;
+
+ default:
+ throw Error('Node type ' + node.nodeType + ' is not supported');
+ }
+ });
+ return df;
+}
diff --git a/chrome/browser/resources/shared/js/parse_html_subset_test.html b/chrome/browser/resources/shared/js/parse_html_subset_test.html
new file mode 100644
index 0000000..54bb3da
--- /dev/null
+++ b/chrome/browser/resources/shared/js/parse_html_subset_test.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title></title>
+<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
+<script src="parse_html_subset.js"></script>
+<script>
+
+goog.require('goog.testing.jsunit');
+
+</script>
+
+</head>
+<body>
+<script>
+
+function parseAndAssertThrows(s) {
+ assertThrows(function() {
+ parseHtmlSubset(s);
+ });
+}
+
+function parseAndAssertNotThrows(s) {
+ assertNotThrows(function() {
+ parseHtmlSubset(s);
+ });
+}
+
+function testText() {
+ parseAndAssertNotThrows('');
+ parseAndAssertNotThrows('abc');
+ parseAndAssertNotThrows('&nbsp;');
+}
+
+function testSupportedTags() {
+ parseAndAssertNotThrows('<b>bold</b>');
+ parseAndAssertNotThrows('Some <b>bold</b> text');
+ parseAndAssertNotThrows('Some <strong>strong</strong> text');
+ parseAndAssertNotThrows('<B>bold</B>');
+ parseAndAssertNotThrows('Some <B>bold</B> text');
+ parseAndAssertNotThrows('Some <STRONG>strong</STRONG> text');
+}
+
+function testInvaliTags() {
+ parseAndAssertThrows('<unknown_tag>x</unknown_tag>');
+ parseAndAssertThrows('<img>');
+ parseAndAssertThrows('<script>alert(1)<' + '/script>');
+}
+
+function testInvalidAttributes() {
+ parseAndAssertThrows('<b onclick="alert(1)">x</b>');
+ parseAndAssertThrows('<b style="color:red">x</b>');
+ parseAndAssertThrows('<b foo>x</b>');
+ parseAndAssertThrows('<b foo=bar></b>');
+}
+
+function testValidAnchors() {
+ parseAndAssertNotThrows('<a href="http://google.com">Google</a>');
+ parseAndAssertNotThrows('<a href="https://google.com">Google</a>');
+}
+
+function testInvalidAnchorHrefs() {
+ parseAndAssertThrows('<a href="ftp://google.com">Google</a>');
+ parseAndAssertThrows('<a href="http/google.com">Google</a>');
+ parseAndAssertThrows('<a href="javascript:alert(1)">Google</a>');
+}
+
+function testInvalidAnchorAttributes() {
+ parseAndAssertThrows('<a name=foo>Google</a>');
+ parseAndAssertThrows(
+ '<a onclick="alert(1)" href="http://google.com">Google</a>');
+ parseAndAssertThrows('<a foo="bar(1)" href="http://google.com">Google</a>');
+}
+
+function testAnchorTarget() {
+ parseAndAssertNotThrows(
+ '<a href="http://google.com" target="blank_">Google</a>');
+ parseAndAssertNotThrows(
+ '<a href="http://google.com" target="foo">Google</a>');
+}
+
+</script>
+
+</body>
+</html>
diff --git a/chrome/common/common_resources.grd b/chrome/common/common_resources.grd
index f7cb3ac..6dda963 100644
--- a/chrome/common/common_resources.grd
+++ b/chrome/common/common_resources.grd
@@ -10,7 +10,7 @@
<release seq="1">
<includes>
<include name="IDR_JSTEMPLATE_JS" file="..\third_party\jstemplate\jstemplate_compiled.js" type="BINDATA" />
- <include name="IDR_I18N_TEMPLATE_JS" file="..\browser\resources\i18n_template.js" type="BINDATA" />
+ <include name="IDR_I18N_TEMPLATE_JS" file="..\browser\resources\shared\js\i18n_template.js" type="BINDATA" />
<include name="IDR_EXTENSION_API_JSON" file="extensions\api\extension_api.json" type="BINDATA" />
</includes>
</release>