summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorarv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-14 22:32:23 +0000
committerarv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-14 22:32:23 +0000
commit4cc5e954e5a7b0c60f88b5461fd953d377ffe5f9 (patch)
tree5a2e69c5caacc2251599c3691ed662e072a28995
parentfdabdc748152405ebdd2f7910ee9d5aebe26588c (diff)
downloadchromium_src-4cc5e954e5a7b0c60f88b5461fd953d377ffe5f9.zip
chromium_src-4cc5e954e5a7b0c60f88b5461fd953d377ffe5f9.tar.gz
chromium_src-4cc5e954e5a7b0c60f88b5461fd953d377ffe5f9.tar.bz2
Bookmark manager: Middle click, Ctrl click, Shift+Control click should open bookmark link in a background tab etc..
BUG=40359 TEST=Middle click on a bookmark link. It should open in a background tab Review URL: http://codereview.chromium.org/1553026 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@44560 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/resources/bookmark_manager/css/bmm.css3
-rw-r--r--chrome/browser/resources/bookmark_manager/js/bmm/bookmarklist.js32
-rw-r--r--chrome/browser/resources/bookmark_manager/js/cr/linkcontroller.js168
-rw-r--r--chrome/browser/resources/bookmark_manager/js/cr/linkcontroller_test.html353
-rw-r--r--chrome/browser/resources/bookmark_manager/js/cr/promise.js50
-rw-r--r--chrome/browser/resources/bookmark_manager/js/cr/promise_test.html30
-rw-r--r--chrome/browser/resources/bookmark_manager/main.html139
-rw-r--r--chrome/chrome_browser.gypi1
8 files changed, 658 insertions, 118 deletions
diff --git a/chrome/browser/resources/bookmark_manager/css/bmm.css b/chrome/browser/resources/bookmark_manager/css/bmm.css
index e9e75e5d7..4a9e48d 100644
--- a/chrome/browser/resources/bookmark_manager/css/bmm.css
+++ b/chrome/browser/resources/bookmark_manager/css/bmm.css
@@ -35,9 +35,6 @@ list > * > * {
list > * > .label {
-webkit-transition: all .15s;
cursor: pointer;
-}
-
-list .label {
color: black;
}
diff --git a/chrome/browser/resources/bookmark_manager/js/bmm/bookmarklist.js b/chrome/browser/resources/bookmark_manager/js/bmm/bookmarklist.js
index a683208..a81ca23 100644
--- a/chrome/browser/resources/bookmark_manager/js/bmm/bookmarklist.js
+++ b/chrome/browser/resources/bookmark_manager/js/bmm/bookmarklist.js
@@ -152,11 +152,10 @@ cr.define('bmm', function() {
handleClick_: function(e) {
var self = this;
- function dispatch(url, kind) {
- var event = self.ownerDocument.createEvent('Event');
- event.initEvent('urlClicked', true, false);
+ function dispatch(url) {
+ var event = new cr.Event('urlClicked', true, false);
event.url = url;
- event.kind = kind;
+ event.originalEvent = e;
self.dispatchEvent(event);
}
@@ -164,8 +163,7 @@ cr.define('bmm', function() {
// Handle clicks on the links to URLs.
if (el.href) {
- dispatch(el.href,
- e.shiftKey ? 'window' : e.button == 1 ? 'tab' : 'self');
+ dispatch(el.href);
// Handle middle click to open bookmark in a new tab.
} else if (e.button == 1) {
@@ -174,7 +172,7 @@ cr.define('bmm', function() {
}
var node = el.bookmarkNode;
if (!bmm.isFolder(node))
- dispatch(node.url, 'tab');
+ dispatch(node.url);
}
},
@@ -460,6 +458,7 @@ cr.define('bmm', function() {
var urlEl = el.childNodes[1];
labelEl.href = urlEl.textContent = bookmarkNode.url;
} else {
+ labelEl.href = '#' + bookmarkNode.id;
el.className = 'folder';
}
}
@@ -470,25 +469,6 @@ cr.define('bmm', function() {
return div;
})();
-
- /**
- * Workaround for http://crbug.com/40902
- * @param {!HTMLElement} list The element to fix the width for.
- */
- function fixListWidth(list) {
- // The width of the list is wrong after its content has changed. Fortunately
- // the reported offsetWidth is correct so we can detect the incorrect width.
- if (list.offsetWidth != list.parentNode.clientWidth - list.offsetLeft) {
- // Set the width to the correct size. This causes the relayout.
- list.style.width = list.parentNode.clientWidth - list.offsetLeft + 'px';
- // Remove the temporary style.width in a timeout. Once the timer fires the
- // size should not change since we already fixed the width.
- window.setTimeout(function() {
- list.style.width = '';
- }, 0);
- }
- }
-
return {
createListItem: createListItem,
BookmarkList: BookmarkList,
diff --git a/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller.js b/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller.js
new file mode 100644
index 0000000..e6241de
--- /dev/null
+++ b/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller.js
@@ -0,0 +1,168 @@
+// 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 file provides a class that can be used to open URLs based
+ * on user interactions. It ensures a consistent behavior when it comes to
+ * holding down Ctrl and Shift while clicking or activating the a link.
+ *
+ * This depends on the {@code chrome.windows} and {@code chrome.tabs}
+ * extensions API.
+ */
+
+cr.define('cr', function() {
+
+ /**
+ * The kind of link open we want to perform.
+ * @enum {number}
+ */
+ const LinkKind = {
+ FOREGROUND_TAB: 0,
+ BACKGROUND_TAB: 1,
+ WINDOW: 2,
+ SELF: 3,
+ INCOGNITO: 4
+ };
+
+ /**
+ * This class is used to handle opening of links based on user actions. The
+ * following actions are currently implemented:
+ *
+ * * Press Ctrl and click a link. Or click a link with your middle mouse
+ * button (or mousewheel). Or press Enter while holding Ctrl.
+ * Opens the link in a new tab in the background .
+ * * Press Ctrl+Shift and click a link. Or press Shift and click a link with
+ * your middle mouse button (or mousewheel). Or press Enter while holding
+ * Ctrl+Shift.
+ * Opens the link in a new tab and switches to the newly opened tab.
+ * * Press Shift and click a link. Or press Enter while holding Shift.
+ * Opens the link in a new window.
+ *
+ * On Mac, uses Command instead of Ctrl.
+ * For keyboard support you need to use keydown.
+ *
+ * @param {!LocalStrings} localStrings The local strings object which is used
+ * to localize the warning prompt in case the user tries to open a lot of
+ * links.
+ * @constructor
+ */
+ function LinkController(localStrings) {
+ this.localStrings_ = localStrings;
+ }
+
+ LinkController.prototype = {
+ /**
+ * The number of links that can be opened before showing a warning confirm
+ * message.
+ */
+ warningLimit: 15,
+
+ /**
+ * The DOM window that we want to open links into in case we are opening
+ * links in the same window.
+ * @type {!Window}
+ */
+ window: window,
+
+ /**
+ * This method is used for showing the warning confirm message when the
+ * user is trying to open a lot of links.
+ * @param {number} The number of URLs to open.
+ * @return {string} The message to show the user.
+ */
+ getWarningMessage: function(count) {
+ return this.localStrings_.getStringF('should_open_all', count);
+ },
+
+ /**
+ * Open an URL from a mouse or keyboard event.
+ * @param {string} url The URL to open.
+ * @param {!Event} e The event triggering the opening of the URL.
+ */
+ openUrlFromEvent: function(url, e) {
+ // We only support keydown Enter and non right click events.
+ if (e.type == 'keydown') {
+ if(e.keyIdentifier != 'Enter')
+ return;
+ } else if (e.type != 'click' || e.button == 2) {
+ return;
+ }
+
+ var kind;
+ var ctrl = cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey;
+
+ if (e.button == 1 || ctrl) // middle, ctrl or keyboard
+ kind = e.shiftKey ? LinkKind.FOREGROUND_TAB : LinkKind.BACKGROUND_TAB;
+ else // left or keyboard
+ kind = e.shiftKey ? LinkKind.WINDOW : LinkKind.SELF;
+
+ this.openUrls([url], kind);
+ },
+
+
+ /**
+ * Opens a URL in a new tab, window or incognito window.
+ * @param {string} url The URL to open.
+ * @param {LinkKind} kind The kind of open we want to do.
+ */
+ openUrl: function (url, kind) {
+ this.openUrls([url], kind);
+ },
+
+ /**
+ * Opens URLs in new tab, window or incognito mode.
+ * @param {!Array.<string>} urls The URLs to open.
+ * @param {LinkKind} kind The kind of open we want to do.
+ */
+ openUrls: function (urls, kind) {
+ if (urls.length < 1)
+ return;
+
+ if (urls.length > this.warningLimit) {
+ if (!this.window.confirm(this.getWarningMessage(urls.length)))
+ return;
+ }
+
+ // Fix '#124' URLs since opening those in a new window does not work. We
+ // prepend the base URL when we encounter those.
+ var base = this.window.location.href.split('#')[0];
+ urls = urls.map(function(url) {
+ return url[0] == '#' ? base + url : url;
+ });
+
+ var incognito = kind == LinkKind.INCOGNITO;
+ if (kind == LinkKind.WINDOW || incognito) {
+ chrome.windows.create({
+ url: urls[0],
+ incognito: incognito
+ }, function(window) {
+ urls.forEach(function(url, i) {
+ if (i > 0)
+ chrome.tabs.create({
+ url: url,
+ windowId: window.id,
+ selected: false
+ });
+ });
+ });
+ } else if (kind == LinkKind.FOREGROUND_TAB ||
+ kind == LinkKind.BACKGROUND_TAB) {
+ urls.forEach(function(url, i) {
+ chrome.tabs.create({
+ url: url,
+ selected: kind == LinkKind.FOREGROUND_TAB && !i
+ });
+ });
+ } else {
+ this.window.location.href = urls[0];
+ }
+ }
+ };
+
+ // Export
+ return {
+ LinkController: LinkController,
+ LinkKind: LinkKind
+ };
+});
diff --git a/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller_test.html b/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller_test.html
new file mode 100644
index 0000000..eb7ebd5
--- /dev/null
+++ b/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller_test.html
@@ -0,0 +1,353 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- TODO(arv): Check in Closue unit tests and make this run as part of the
+ tests -->
+<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
+<script src="../cr.js"></script>
+<script src="linkcontroller.js"></script>
+<script>
+
+goog.require('goog.testing.MockControl');
+goog.require('goog.testing.PropertyReplacer');
+goog.require('goog.testing.jsunit');
+goog.require('goog.testing.mockmatchers');
+
+</script>
+</head>
+<body>
+<script>
+
+var mockControl, propertyReplacer, mockWindow;
+var chrome = chrome || {};
+chrome.tabs = chrome.tabs || {};
+chrome.windows = chrome.windows || {};
+
+var ObjectEqualsMatcher = goog.testing.mockmatchers.ObjectEquals;
+var SaveArgumentMatcher = goog.testing.mockmatchers.SaveArgument;
+
+var MSG = 'MSG';
+var localStrings = {
+ getStringF: function(msg, number) {
+ assertEquals('should_open_all', msg);
+ return MSG + number;
+ }
+};
+
+var URL1 = 'http://chromium.org/';
+var URL2 = '#hash';
+var WINDOW_ID = 'WINDOW_ID';
+
+function setUp() {
+ mockControl = new goog.testing.MockControl;
+ chrome.tabs.create = mockControl.createFunctionMock();
+ chrome.windows.create = mockControl.createFunctionMock();
+
+ propertyReplacer = new goog.testing.PropertyReplacer;
+
+ mockWindow = {
+ confirm: mockControl.createFunctionMock(),
+ location: {
+ get href() {
+ return 'http://www.google.com/';
+ },
+ set href(url) {
+ assertEquals(URL1, url);
+ }
+ }
+ };
+}
+
+function tearDown() {
+ mockControl.$tearDown();
+ propertyReplacer.reset();
+}
+
+function testGetWarningMessage() {
+ var lc = new cr.LinkController(localStrings);
+ var msg = lc.getWarningMessage(10);
+ assertEquals(MSG + 10, msg);
+}
+
+function openUrlFromEventHelper(event, isMac, expectedKind) {
+ var lc = new cr.LinkController(localStrings);
+
+ lc.openUrls = mockControl.createFunctionMock();
+ lc.openUrls([URL1], expectedKind);
+
+ propertyReplacer.set(cr, 'isMac', isMac);
+
+ mockControl.$replayAll();
+
+ lc.openUrlFromEvent(URL1, event);
+
+ mockControl.$verifyAll();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+function testOpenUrlFromEventForegroundTab() {
+ var e = {
+ type: 'click',
+ button: 0,
+ shiftKey: true,
+ ctrlKey: true
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.FOREGROUND_TAB);
+}
+
+function testOpenUrlFromEventForegroundTabMac() {
+ var e = {
+ type: 'click',
+ button: 0,
+ shiftKey: true,
+ metaKey: true
+ };
+ openUrlFromEventHelper(e, true, cr.LinkKind.FOREGROUND_TAB);
+}
+
+function testOpenUrlFromEventForegroundTabEnter() {
+ var e = {
+ type: 'keydown',
+ keyIdentifier: 'Enter',
+ shiftKey: true,
+ ctrlKey: true
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.FOREGROUND_TAB);
+}
+
+function testOpenUrlFromEventForegroundTabEnterMac() {
+ var e = {
+ type: 'keydown',
+ keyIdentifier: 'Enter',
+ shiftKey: true,
+ metaKey: true
+ };
+ openUrlFromEventHelper(e, true, cr.LinkKind.FOREGROUND_TAB);
+}
+
+function testOpenUrlFromEventForegroundTabMiddleClick() {
+ var e = {
+ type: 'click',
+ button: 1,
+ shiftKey: true
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.FOREGROUND_TAB);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+function testOpenUrlFromEventBackgroundTab() {
+ var e = {
+ type: 'click',
+ button: 0,
+ ctrlKey: true
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.BACKGROUND_TAB);
+}
+
+function testOpenUrlFromEventBackgroundTabMac() {
+ var e = {
+ type: 'click',
+ button: 0,
+ metaKey: true
+ };
+ openUrlFromEventHelper(e, true, cr.LinkKind.BACKGROUND_TAB);
+}
+
+function testOpenUrlFromEventBackgroundTabEnter() {
+ var e = {
+ type: 'keydown',
+ keyIdentifier: 'Enter',
+ ctrlKey: true
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.BACKGROUND_TAB);
+}
+
+function testOpenUrlFromEventBackgroundTabEnterMac() {
+ var e = {
+ type: 'keydown',
+ keyIdentifier: 'Enter',
+ metaKey: true
+ };
+ openUrlFromEventHelper(e, true, cr.LinkKind.BACKGROUND_TAB);
+}
+
+function testOpenUrlFromEventBackgroundTabMiddleClick() {
+ var e = {
+ type: 'click',
+ button: 1
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.BACKGROUND_TAB);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+function testOpenUrlFromEventWindow() {
+ var e = {
+ type: 'click',
+ button: 0,
+ shiftKey: true
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.WINDOW);
+}
+
+function testOpenUrlFromEventWindowEnter() {
+ var e = {
+ type: 'keydown',
+ keyIdentifier: 'Enter',
+ shiftKey: true
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.WINDOW);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+function testOpenUrlFromEventSelf() {
+ var e = {
+ type: 'click',
+ button: 0
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.SELF);
+}
+
+function testOpenUrlFromEventSelfEnter() {
+ var e = {
+ type: 'keydown',
+ keyIdentifier: 'Enter'
+ };
+ openUrlFromEventHelper(e, false, cr.LinkKind.SELF);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+function testOpenUrl() {
+ var lc = new cr.LinkController(localStrings);
+ lc.openUrls = mockControl.createFunctionMock();
+
+ lc.openUrls(new ObjectEqualsMatcher([URL1]), cr.LinkKind.SELF);
+ mockControl.$replayAll();
+
+ lc.openUrl(URL1, cr.LinkKind.SELF);
+
+ mockControl.$verifyAll();
+}
+
+/////////////////////////////// OpenUrls //////////////////////////////////////
+
+function testOpenUrlsTooFew() {
+ var lc = new cr.LinkController(localStrings);
+
+ mockControl.$replayAll();
+
+ lc.openUrls([], cr.LinkKind.SELF);
+
+ mockControl.$verifyAll();
+}
+
+
+function testOpenUrlsTooMany() {
+ var lc = new cr.LinkController(localStrings);
+ lc.warningLimit = 9;
+
+ var urls = new Array(lc.warningLimit + 1);
+ lc.window = mockWindow;
+
+ lc.window.confirm('MSG10').$returns(false);
+ mockControl.$replayAll();
+
+ lc.openUrls(urls, cr.LinkKind.SELF);
+
+ mockControl.$verifyAll();
+}
+
+function testOpenUrlsSelf() {
+ var lc = new cr.LinkController(localStrings);
+ lc.window = mockWindow;
+
+ mockControl.$replayAll();
+
+ lc.openUrls([URL1], cr.LinkKind.SELF);
+
+ mockControl.$verifyAll();
+}
+
+function testOpenUrlsForegroundTab() {
+ var lc = new cr.LinkController(localStrings);
+ lc.window = mockWindow;
+
+ chrome.tabs.create(new ObjectEqualsMatcher({url: URL1, selected: true}));
+ chrome.tabs.create(new ObjectEqualsMatcher({
+ url: 'http://www.google.com/#hash',
+ selected: false
+ }));
+
+ mockControl.$replayAll();
+
+ lc.openUrls([URL1, URL2], cr.LinkKind.FOREGROUND_TAB);
+
+ mockControl.$verifyAll();
+}
+
+function testOpenUrlsBackgroundTab() {
+ var lc = new cr.LinkController(localStrings);
+ lc.window = mockWindow;
+
+ chrome.tabs.create(new ObjectEqualsMatcher({url: URL1, selected: false}));
+ chrome.tabs.create(new ObjectEqualsMatcher({
+ url: 'http://www.google.com/#hash',
+ selected: false
+ }));
+
+ mockControl.$replayAll();
+
+ lc.openUrls([URL1, URL2], cr.LinkKind.BACKGROUND_TAB);
+
+ mockControl.$verifyAll();
+}
+
+function testOpenUrlsWindow() {
+ var lc = new cr.LinkController(localStrings);
+ lc.window = mockWindow;
+
+ var callbackMatcher = new SaveArgumentMatcher;
+ chrome.windows.create(new ObjectEqualsMatcher({url: URL1, incognito: false}),
+ callbackMatcher);
+ chrome.tabs.create(new ObjectEqualsMatcher({
+ url: 'http://www.google.com/#hash',
+ windowId: WINDOW_ID,
+ selected: false
+ }));
+
+ mockControl.$replayAll();
+
+ lc.openUrls([URL1, URL2], cr.LinkKind.WINDOW);
+ callbackMatcher.arg({id: WINDOW_ID});
+
+ mockControl.$verifyAll();
+}
+
+function testOpenUrlsIncognito() {
+ var lc = new cr.LinkController(localStrings);
+ lc.window = mockWindow;
+
+ var callbackMatcher = new SaveArgumentMatcher;
+ chrome.windows.create(new ObjectEqualsMatcher({url: URL1, incognito: true}),
+ callbackMatcher);
+ chrome.tabs.create(new ObjectEqualsMatcher({
+ url: 'http://www.google.com/#hash',
+ windowId: WINDOW_ID,
+ selected: false
+ }));
+
+ mockControl.$replayAll();
+
+ lc.openUrls([URL1, URL2], cr.LinkKind.INCOGNITO);
+ callbackMatcher.arg({id: WINDOW_ID});
+
+ mockControl.$verifyAll();
+}
+
+</script>
+</body>
+</html>
diff --git a/chrome/browser/resources/bookmark_manager/js/cr/promise.js b/chrome/browser/resources/bookmark_manager/js/cr/promise.js
index 3e8944f..a9d233a 100644
--- a/chrome/browser/resources/bookmark_manager/js/cr/promise.js
+++ b/chrome/browser/resources/bookmark_manager/js/cr/promise.js
@@ -15,16 +15,20 @@ cr.define('cr', function() {
/**
* Creates a future promise.
- * @param {Function=} opt_callback Callback.
+ * @param {*=} opt_value The value to set the promise to. If set completes
+ * the promise immediately.
* @constructor
*/
- function Promise(opt_callback) {
+ function Promise(opt_value) {
/**
* An array of the callbacks.
* @type {!Array.<!Function>}
* @private
*/
- this.callbacks_ = opt_callback ? [opt_callback] : [];
+ this.callbacks_ = [];
+
+ if (arguments.length > 0)
+ this.value = opt_value;
}
Promise.prototype = {
@@ -133,10 +137,11 @@ cr.define('cr', function() {
/**
* Creates a new future promise that is fulfilled when any of the promises are
- * fulfilled.
+ * fulfilled. The value of the returned promise will be the value of the first
+ * fulfilled promise.
* @param {...!Promise} var_args The promises used to build up the new
* promise.
- * @return {!Promise} The new promise that will be fulfilled when any of th
+ * @return {!Promise} The new promise that will be fulfilled when any of the
* passed in promises are fulfilled.
*/
Promise.any = function(var_args) {
@@ -151,6 +156,41 @@ cr.define('cr', function() {
};
/**
+ * Creates a new future promise that is fulfilled when all of the promises are
+ * fulfilled. The value of the returned promise is an array of the values of
+ * the promises passed in.
+ * @param {...!Promise} var_args The promises used to build up the new
+ * promise.
+ * @return {!Promise} The promise that wraps all the promises in the array.
+ */
+ Promise.all = function(var_args) {
+ var p = new Promise;
+ var args = Array.prototype.slice.call(arguments);
+ var count = args.length;
+ if (!count) {
+ p.value = [];
+ return p;
+ }
+
+ function f(v) {
+ count--;
+ if (!count) {
+ p.value = args.map(function(argP) {
+ return argP.value;
+ });
+ }
+ }
+
+ // Do not use count here since count may be decremented in the call to
+ // addListener if the promise is already done.
+ for (var i = 0; i < args.length; i++) {
+ args[i].addListener(f);
+ }
+
+ return p;
+ };
+
+ /**
* Wraps an event in a future promise.
* @param {!EventTarget} target The object that dispatches the event.
* @param {string} type The type of the event.
diff --git a/chrome/browser/resources/bookmark_manager/js/cr/promise_test.html b/chrome/browser/resources/bookmark_manager/js/cr/promise_test.html
index 19e3d1a..5c9ae1d 100644
--- a/chrome/browser/resources/bookmark_manager/js/cr/promise_test.html
+++ b/chrome/browser/resources/bookmark_manager/js/cr/promise_test.html
@@ -107,9 +107,9 @@ function testCallbacks4() {
calls2++;
assertEquals(V, v);
}
- var p = new Promise(f1);
+ var p = new Promise(V);
+ p.addListener(f1);
p.addListener(f2);
- p.value = V;
assertEquals(1, calls1);
assertEquals(1, calls2);
}
@@ -195,6 +195,32 @@ function testAny() {
assertEquals(2, any.value);
}
+function testAll() {
+ var p1 = new Promise;
+ var p2 = new Promise;
+ var p3 = new Promise;
+
+ var pAll = Promise.all(p1, p2, p3);
+ p1.value = 1;
+ p2.value = 2;
+ p3.value = 3;
+ assertArrayEquals([1, 2, 3], pAll.value);
+}
+
+function testAllEmpty() {
+ var pAll = Promise.all();
+ assertArrayEquals([], pAll.value);
+}
+
+function testAllAlreadyDone() {
+ var p1 = new Promise(1);
+ var p2 = new Promise(2);
+ var p3 = new Promise(3);
+
+ var pAll = Promise.all(p1, p2, p3);
+ assertArrayEquals([1, 2, 3], pAll.value);
+}
+
function testEvent() {
var p = Promise.event(document.body, 'foo');
var e = new cr.Event('foo');
diff --git a/chrome/browser/resources/bookmark_manager/main.html b/chrome/browser/resources/bookmark_manager/main.html
index 9a12c77..ad947b0 100644
--- a/chrome/browser/resources/bookmark_manager/main.html
+++ b/chrome/browser/resources/bookmark_manager/main.html
@@ -21,6 +21,7 @@ found in the LICENSE file.
<script src="js/cr.js"></script>
<script src="js/cr/event.js"></script>
<script src="js/cr/eventtarget.js"></script>
+<script src="js/cr/linkcontroller.js"></script>
<script src="js/cr/promise.js"></script>
<script src="js/cr/ui.js"></script>
<script src="js/cr/ui/listselectionmodel.js"></script>
@@ -86,6 +87,7 @@ const BookmarkList = bmm.BookmarkList;
const BookmarkTree = bmm.BookmarkTree;
const ListItem = cr.ui.ListItem;
const TreeItem = cr.ui.TreeItem;
+const LinkKind = cr.LinkKind;
/**
* The id of the bookmark root.
@@ -236,16 +238,16 @@ window.onhashchange = function(e) {
}
};
-// Activate is handled by the activate-command.
+// Activate is handled by the open-in-same-window-command.
list.addEventListener('dblclick', function(e) {
if (e.button == 0)
- $('activate-command').execute();
+ $('open-in-same-window-command').execute();
});
// The list dispatches an event when the user clicks on the URL or the Show in
// folder part.
list.addEventListener('urlClicked', function(e) {
- openUrls([e.url], e.kind);
+ getLinkController().openUrlFromEvent(e.url, e.originalEvent);
});
/**
@@ -923,12 +925,10 @@ dnd.init();
<!-- open * are handled in canExecute handler -->
<command id="open-in-new-tab-command"></command>
+<command id="open-in-background-tab-command"></command>
<command id="open-in-new-window-command"></command>
<command id="open-incognito-window-command"></command>
-
-<!-- Activate command is used by enter and dblclick and it eaither navigates to
- a folder or execute one of the open commands. -->
-<command id="activate-command"></command>
+<command id="open-in-same-window-command"></command>
<!-- TODO(arv): I think the commands might be better created in code? -->
@@ -977,6 +977,7 @@ const Command = cr.ui.Command;
const CommandBinding = cr.ui.CommandBinding;
const Menu = cr.ui.Menu;
const MenuButton = cr.ui.MenuButton;
+const Promise = cr.Promise;
cr.ui.decorate('menu', Menu);
cr.ui.decorate('button[menu]', MenuButton);
@@ -1146,12 +1147,13 @@ list.addEventListener('canExecute', function(e) {
break;
case 'open-in-new-tab-command':
+ case 'open-in-background-tab-command':
case 'open-in-new-window-command':
case 'open-incognito-window-command':
updateOpenCommands(e, command);
break;
- case 'activate-command':
+ case 'open-in-same-window-command':
e.canExecute = hasSelected();
break;
}
@@ -1205,6 +1207,7 @@ tree.addEventListener('canExecute', function(e) {
break;
case 'open-in-new-tab-command':
+ case 'open-in-background-tab-command':
case 'open-in-new-window-command':
case 'open-incognito-window-command':
updateOpenCommands(e, command);
@@ -1221,7 +1224,7 @@ function updateCommandsBasedOnSelection(e) {
// Paste only needs to updated when the tree selection changes.
var commandNames = ['copy', 'cut', 'delete', 'rename-folder', 'edit',
'add-new-bookmark', 'new-folder', 'open-in-new-tab',
- 'open-in-new-window', 'open-incognito-window', 'activate'];
+ 'open-in-new-window', 'open-incognito-window', 'open-in-same-window'];
if (e.target == tree) {
commandNames.push('paste', 'show-in-folder', 'sort');
@@ -1313,49 +1316,15 @@ function showInFolder() {
navigateTo(parentId);
}
+var linkController;
+
/**
- * Opens URLs in new tab, window or incognito mode.
- * @param {!Array.<string>} urls The URLs to open.
- * @param {string} kind The kind is either 'tab', 'window', or 'incognito'.
+ * @return {!cr.LinkController} The link controller used to open links based on
+ * user clicks and keyboard actions.
*/
-function openUrls(urls, kind) {
- if (urls.length < 1)
- return;
-
- if (urls.length > 15) {
- if (!confirm(localStrings.getStringF('should_open_all', urls.length)))
- return;
- }
-
- // Fix '#124' URLs since open those in a new window does not work. We prepend
- // the base URL when we encounter those.
- var base = window.location.href.split('#')[0];
- urls = urls.map(function(url) {
- return url[0] == '#' ? base + url : url;
- });
-
- var incognito = kind == 'incognito';
- if (kind == 'window' || incognito) {
- chrome.windows.create({
- url: urls[0],
- incognito: incognito
- }, function(window) {
- urls.forEach(function(url, i) {
- if (i > 0)
- chrome.tabs.create({
- url: url,
- windowId: window.id,
- selected: false
- });
- });
- });
- } else if (kind == 'tab') {
- urls.forEach(function(url, i) {
- chrome.tabs.create({url: url, selected: !i});
- });
- } else {
- window.location.href = urls[0];
- }
+function getLinkController() {
+ return linkController ||
+ (linkController = new cr.LinkController(localStrings));
}
/**
@@ -1386,28 +1355,14 @@ function getSelectedBookmarkIds() {
/**
* Opens the selected bookmarks.
+ * @param {LinkKind} kind The kind of link we want to open.
*/
function openBookmarks(kind) {
// If we have selected any folders we need to find all items recursively.
- // We can do several async calls to getChildren but instead we do a single
- // call to getTree and only add the subtrees of the selected items.
+ // We use multiple async calls to getSubtree instead of getting the whole
+ // tree since we would like to minimize the amount of data sent.
var urls = [];
- var idMap = {};
-
- // Traverses the tree until it finds a node tree that should be added. Then
- // we switch over to use addNodes. We could merge these two functions into
- // one but that would make the code less readable.
- function traverseNodes(node) {
- // This is not using the iterator since it uses breadth first search.
- if (node.id in idMap) {
- addNodes(node);
- } else if (node.children) {
- for (var i = 0; i < node.children.length; i++) {
- traverseNodes(node.children[i]);
- }
- }
- }
// Adds the node and all the descendants
function addNodes(node) {
@@ -1421,14 +1376,23 @@ function openBookmarks(kind) {
var nodes = getSelectedBookmarkNodes();
- // Create a map for simpler lookup later.
- nodes.forEach(function(node) {
- idMap[node.id] = true;
+ // Get a future promise for every selected item.
+ var promises = nodes.map(function(node) {
+ if (bmm.isFolder(node))
+ return bmm.loadSubtree(node.id);
+ // Not a folder so we already have all the data we need.
+ return new Promise(node.url);
});
- var p = bmm.loadTree();
- p.addListener(function(node) {
- traverseNodes(node);
- openUrls(urls, kind);
+
+ var p = Promise.all.apply(null, promises);
+ p.addListener(function(values) {
+ values.forEach(function(v) {
+ if (typeof v == 'string')
+ urls.push(v);
+ else
+ addNodes(v);
+ });
+ getLinkController().openUrls(urls, kind);
});
}
@@ -1441,7 +1405,7 @@ function openItem() {
if (bookmarkNodes.length == 1 && bmm.isFolder(bookmarkNodes[0])) {
navigateTo(bookmarkNodes[0].id);
} else {
- openBookmarks('tab');
+ openBookmarks(LinkKind.FOREGROUND_TAB);
}
}
@@ -1506,13 +1470,16 @@ function handleCommand(e) {
showInFolder();
break;
case 'open-in-new-tab-command':
- openBookmarks('tab');
+ openBookmarks(LinkKind.FOREGROUND_TAB);
+ break;
+ case 'open-in-background-tab-command':
+ openBookmarks(LinkKind.BACKGROUND_TAB);
break;
case 'open-in-new-window-command':
- openBookmarks('window');
+ openBookmarks(LinkKind.WINDOW);
break;
case 'open-incognito-window-command':
- openBookmarks('incognito');
+ openBookmarks(LinkKind.INCOGNITO);
break;
case 'delete-command':
deleteBookmarks();
@@ -1539,7 +1506,7 @@ function handleCommand(e) {
case 'add-new-bookmark-command':
addPage();
break;
- case 'activate-command':
+ case 'open-in-same-window-command':
openItem();
break;
}
@@ -1547,11 +1514,19 @@ function handleCommand(e) {
// Delete on all platforms. On Mac we also allow Meta+Backspace.
$('delete-command').shortcut = 'U+007F' +
- (cr.isMac ? ' U+0008 U+0008-meta' : '');
+ (cr.isMac ? ' U+0008 Meta-U+0008' : '');
// Enter on all platforms. Mac we also support space and Command-Down !?!
-$('activate-command').shortcut = 'Enter' +
- (cr.isMac ? ' U+0020 Down-meta' : '');
+$('open-in-same-window-command').shortcut = 'Enter' +
+ (cr.isMac ? ' U+0020 Meta-Down' : '');
+
+//
+$('open-in-new-window-command').shortcut = 'Shift-Enter';
+$('open-in-background-tab-command').shortcut = cr.isMac ? 'Meta-Enter' :
+ 'Ctrl-Enter';
+$('open-in-new-tab-command').shortcut = cr.isMac ? 'Shift-Meta-Enter' :
+ 'Shift-Ctrl-Enter';
+
if (!cr.isMac) {
$('rename-folder-command').shortcut = 'F2';
$('edit-command').shortcut = 'F2';
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 4601b8b..b317623 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -3161,6 +3161,7 @@
'files': [
'browser/resources/bookmark_manager/js/cr/event.js',
'browser/resources/bookmark_manager/js/cr/eventtarget.js',
+ 'browser/resources/bookmark_manager/js/cr/linkcontroller.js',
'browser/resources/bookmark_manager/js/cr/promise.js',
'browser/resources/bookmark_manager/js/cr/ui.js',
]