From fbd17cf5288dec78851ada15fadfda0563419833 Mon Sep 17 00:00:00 2001 From: "feldstein@chromium.org" Date: Wed, 28 Apr 2010 23:52:56 +0000 Subject: Rework of the shared resources patch for checkin Implement shared resources and use them in bookmark manager I had an issue with git when moving files so i had to create a new patch to delete/readd them here. See code review 1564034. This is just for trybots/checkins. BUG=none TEST=none Review URL: http://codereview.chromium.org/1694019 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@45885 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/browser/dom_ui/chrome_url_data_manager.cc | 13 + .../resources/bookmark_manager/css/list.css | 62 -- .../resources/bookmark_manager/css/menu.css | 48 -- .../resources/bookmark_manager/css/tree.css | 153 ----- .../resources/bookmark_manager/css/tree.css.js | 22 - chrome/browser/resources/bookmark_manager/js/cr.js | 314 ---------- .../resources/bookmark_manager/js/cr/event.js | 43 -- .../bookmark_manager/js/cr/eventtarget.js | 104 ---- .../bookmark_manager/js/cr/eventtarget_test.html | 139 ----- .../bookmark_manager/js/cr/linkcontroller.js | 168 ------ .../js/cr/linkcontroller_test.html | 353 ----------- .../resources/bookmark_manager/js/cr/promise.js | 213 ------- .../bookmark_manager/js/cr/promise_test.html | 251 -------- .../browser/resources/bookmark_manager/js/cr/ui.js | 173 ------ .../resources/bookmark_manager/js/cr/ui/command.js | 295 --------- .../js/cr/ui/contextmenuhandler.js | 225 ------- .../resources/bookmark_manager/js/cr/ui/list.js | 265 -------- .../bookmark_manager/js/cr/ui/listitem.js | 58 -- .../js/cr/ui/listselectionmodel.js | 450 -------------- .../resources/bookmark_manager/js/cr/ui/menu.js | 157 ----- .../bookmark_manager/js/cr/ui/menubutton.js | 167 ------ .../bookmark_manager/js/cr/ui/menuitem.js | 147 ----- .../bookmark_manager/js/cr/ui/splitter.js | 154 ----- .../resources/bookmark_manager/js/cr/ui/tree.js | 664 --------------------- .../resources/bookmark_manager/js/cr_test.html | 231 ------- .../resources/bookmark_manager/js/i18ntemplate.js | 104 ---- .../resources/bookmark_manager/js/localstrings.js | 55 -- .../browser/resources/bookmark_manager/js/util.js | 57 -- .../browser/resources/bookmark_manager/main.html | 49 +- .../resources/bookmark_manager/manifest.json | 3 +- chrome/browser/resources/shared/css/list.css | 62 ++ chrome/browser/resources/shared/css/menu.css | 48 ++ chrome/browser/resources/shared/css/tree.css | 153 +++++ chrome/browser/resources/shared/css/tree.css.js | 22 + chrome/browser/resources/shared/js/cr.js | 314 ++++++++++ chrome/browser/resources/shared/js/cr/event.js | 43 ++ .../browser/resources/shared/js/cr/eventtarget.js | 104 ++++ .../resources/shared/js/cr/eventtarget_test.html | 139 +++++ .../resources/shared/js/cr/linkcontroller.js | 168 ++++++ .../shared/js/cr/linkcontroller_test.html | 353 +++++++++++ chrome/browser/resources/shared/js/cr/promise.js | 213 +++++++ .../resources/shared/js/cr/promise_test.html | 251 ++++++++ chrome/browser/resources/shared/js/cr/ui.js | 173 ++++++ .../browser/resources/shared/js/cr/ui/command.js | 295 +++++++++ .../shared/js/cr/ui/contextmenuhandler.js | 225 +++++++ chrome/browser/resources/shared/js/cr/ui/list.js | 265 ++++++++ .../browser/resources/shared/js/cr/ui/listitem.js | 58 ++ .../shared/js/cr/ui/listselectionmodel.js | 450 ++++++++++++++ chrome/browser/resources/shared/js/cr/ui/menu.js | 157 +++++ .../resources/shared/js/cr/ui/menubutton.js | 167 ++++++ .../browser/resources/shared/js/cr/ui/menuitem.js | 147 +++++ .../browser/resources/shared/js/cr/ui/splitter.js | 154 +++++ chrome/browser/resources/shared/js/cr/ui/tree.js | 664 +++++++++++++++++++++ chrome/browser/resources/shared/js/cr_test.html | 231 +++++++ chrome/browser/resources/shared/js/i18ntemplate.js | 104 ++++ chrome/browser/resources/shared/js/localstrings.js | 55 ++ chrome/browser/resources/shared/js/util.js | 57 ++ chrome/chrome_browser.gypi | 80 ++- chrome/chrome_dll.gypi | 1 + chrome/common/chrome_paths.cc | 10 + chrome/common/chrome_paths.h | 3 + chrome/common/extensions/extension.cc | 24 +- chrome/common/extensions/extension.h | 6 + .../extensions/extension_manifests_unittest.cc | 19 + chrome/common/url_constants.cc | 1 + chrome/common/url_constants.h | 1 + .../permission_chrome_resources_url.json | 7 + 67 files changed, 5226 insertions(+), 5135 deletions(-) delete mode 100644 chrome/browser/resources/bookmark_manager/css/list.css delete mode 100644 chrome/browser/resources/bookmark_manager/css/menu.css delete mode 100644 chrome/browser/resources/bookmark_manager/css/tree.css delete mode 100644 chrome/browser/resources/bookmark_manager/css/tree.css.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/event.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/eventtarget.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/eventtarget_test.html delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/linkcontroller.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/linkcontroller_test.html delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/promise.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/promise_test.html delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/command.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/contextmenuhandler.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/list.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/listitem.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/listselectionmodel.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/menu.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/menubutton.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/menuitem.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/splitter.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr/ui/tree.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/cr_test.html delete mode 100644 chrome/browser/resources/bookmark_manager/js/i18ntemplate.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/localstrings.js delete mode 100644 chrome/browser/resources/bookmark_manager/js/util.js create mode 100644 chrome/browser/resources/shared/css/list.css create mode 100644 chrome/browser/resources/shared/css/menu.css create mode 100644 chrome/browser/resources/shared/css/tree.css create mode 100644 chrome/browser/resources/shared/css/tree.css.js create mode 100644 chrome/browser/resources/shared/js/cr.js create mode 100644 chrome/browser/resources/shared/js/cr/event.js create mode 100644 chrome/browser/resources/shared/js/cr/eventtarget.js create mode 100644 chrome/browser/resources/shared/js/cr/eventtarget_test.html create mode 100644 chrome/browser/resources/shared/js/cr/linkcontroller.js create mode 100644 chrome/browser/resources/shared/js/cr/linkcontroller_test.html create mode 100644 chrome/browser/resources/shared/js/cr/promise.js create mode 100644 chrome/browser/resources/shared/js/cr/promise_test.html create mode 100644 chrome/browser/resources/shared/js/cr/ui.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/command.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/contextmenuhandler.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/list.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/listitem.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/listselectionmodel.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/menu.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/menubutton.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/menuitem.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/splitter.js create mode 100644 chrome/browser/resources/shared/js/cr/ui/tree.js create mode 100644 chrome/browser/resources/shared/js/cr_test.html create mode 100644 chrome/browser/resources/shared/js/i18ntemplate.js create mode 100644 chrome/browser/resources/shared/js/localstrings.js create mode 100644 chrome/browser/resources/shared/js/util.js create mode 100644 chrome/test/data/extensions/manifest_tests/permission_chrome_resources_url.json (limited to 'chrome') diff --git a/chrome/browser/dom_ui/chrome_url_data_manager.cc b/chrome/browser/dom_ui/chrome_url_data_manager.cc index 232a993..ea22d76 100644 --- a/chrome/browser/dom_ui/chrome_url_data_manager.cc +++ b/chrome/browser/dom_ui/chrome_url_data_manager.cc @@ -98,6 +98,13 @@ void RegisterURLRequestChromeJob() { chrome::kChromeUIDevToolsHost, inspector_dir); } + // Set up the chrome://resources/ source. + FilePath resources_dir; + if (PathService::Get(chrome::DIR_SHARED_RESOURCES, &resources_dir)) { + Singleton()->AddFileSource( + chrome::kChromeUIResourcesHost, resources_dir); + } + URLRequest::RegisterProtocolFactory(chrome::kChromeUIScheme, &ChromeURLDataManager::Factory); URLRequest::RegisterProtocolFactory(chrome::kPrintScheme, @@ -110,6 +117,12 @@ void UnregisterURLRequestChromeJob() { Singleton()->RemoveFileSource( chrome::kChromeUIDevToolsHost); } + + FilePath resources_dir; + if (PathService::Get(chrome::DIR_SHARED_RESOURCES, &resources_dir)) { + Singleton()->RemoveFileSource( + chrome::kChromeUIResourcesHost); + } } // static diff --git a/chrome/browser/resources/bookmark_manager/css/list.css b/chrome/browser/resources/bookmark_manager/css/list.css deleted file mode 100644 index 5f91834..0000000 --- a/chrome/browser/resources/bookmark_manager/css/list.css +++ /dev/null @@ -1,62 +0,0 @@ - -list { - overflow: auto; - outline: none; -} - -list > * { - -webkit-user-select: none; - background-color: rgba(255,255,255,0); - border: 1px solid rgba(255,255,255,0); /* transparent white */ - border-radius: 2px; - cursor: default; - line-height: 20px; - margin: -1px 0; - overflow: hidden; - padding: 0px 3px; - position: relative; /* to allow overlap */ - text-overflow: ellipsis; - white-space: pre; -} - -list > [lead] { - border-color: transparent; -} - -list:focus > [lead] { - border-color: hsl(214, 91%, 65%); - z-index: 2; -} - -list > [anchor] { - -} - -list > :hover { - border-color: hsl(214, 91%, 85%); - z-index: 1; - background-color: hsl(214, 91%, 97%); -} - -list > [selected] { - background-image: -webkit-gradient(linear, left top, left bottom, - from(rgba(255,255,255,0.8)), to(rgba(255,255,255,0))); -} - -list > [selected] { - border-color: hsl(0, 0%, 85%); - background-color: hsl(0,0%,90%); - z-index: 2; -} - -list:focus > [selected] { - background-color: hsl(214,91%,89%); - border-color: hsl(214, 91%, 65%); -} - -list:focus > [lead][selected], -list > [selected]:hover { - background-color: hsl(214, 91%, 87%); - border-color: hsl(214, 91%, 65%); -} - diff --git a/chrome/browser/resources/bookmark_manager/css/menu.css b/chrome/browser/resources/bookmark_manager/css/menu.css deleted file mode 100644 index ef35992..0000000 --- a/chrome/browser/resources/bookmark_manager/css/menu.css +++ /dev/null @@ -1,48 +0,0 @@ - -menu { - display: none; - position: absolute; - border: 1px solid #999; - -webkit-box-shadow: 2px 2px 3px hsla(0, 0%, 0%, .3); - color: black; - background-color: white; - left: 0; - white-space: nowrap; - z-index: 2; - padding: 2px; - margin: 0; - cursor: default; - border-radius: 4px; -} - -menu > * { - display: block; - margin: 0; - width: 100%; - text-align: start; -} - -menu > :not(hr) { - -webkit-appearance: none; - background: transparent; - font: inherit; - border: 0; - padding: 3px 8px; - overflow: hidden; - text-overflow: ellipsis; -} - -menu > hr { - border: 0; - border-top: 1px solid rgb(153, 153, 153); - margin: 2px 0; -} - -menu > [hidden] { - display: none; -} - -menu > [selected] { - background-color: hsl(213, 66%, 57%); - color: white; -} diff --git a/chrome/browser/resources/bookmark_manager/css/tree.css b/chrome/browser/resources/bookmark_manager/css/tree.css deleted file mode 100644 index 10bb708..0000000 --- a/chrome/browser/resources/bookmark_manager/css/tree.css +++ /dev/null @@ -1,153 +0,0 @@ -tree { - outline: none; - overflow: auto; - display: block; -} - -.tree-item > .tree-row { - color: black; - -webkit-user-select: none; - border: 1px solid rgba(255,255,255,0); /* transparent white */ - background-color: rgba(255,255,255,0); - border-radius: 2px; - padding: 0px 3px; - line-height: 20px; - white-space: nowrap; - cursor: default; - position: relative; - margin: -1px 0; -} - -.expand-icon { - width: 16px; - height: 16px; - display: inline-block; - vertical-align: top; - position: relative; - top: 2px; - background-image: -webkit-canvas(tree-triangle); - background-position: 50% 50%; - background-repeat: no-repeat; - -webkit-transition: all .15s; - opacity: .6; - -webkit-transform: rotate(-90deg); -} - -html[dir=rtl] .expand-icon { - -webkit-transform: rotate(90deg); -} - -.tree-item[expanded] > .tree-row > .expand-icon { - background-image: -webkit-canvas(tree-triangle); - -webkit-transform: rotate(0deg); - opacity: .5; -} - -.tree-row .expand-icon { - visibility: hidden; -} - -.tree-row[may-have-children] .expand-icon { - visibility: visible; -} - -.tree-row[has-children=false] .expand-icon { - visibility: hidden; -} - -.tree-row:hover { - border-color: hsl(214, 91%, 85%); - z-index: 1; - background-color: hsl(214, 91%, 97%); -} - -/* - WebKit has a bug with attribute selectors so we apply selected to the tree row - as well. - - https://bugs.webkit.org/show_bug.cgi?id=12519 - -*/ -.tree-row[selected] { - background-image: -webkit-gradient(linear, left top, left bottom, - from(rgba(255,255,255,0.8)), to(rgba(255,255,255,0))); -} - -.tree-row[selected] { - border-color: hsl(0,0%,85%); - background-color: hsl(0, 0%, 90%); - z-index: 2; -} - -.tree-row[selected]:hover, -tree:focus .tree-row[selected] { - background-color: hsl(214, 91%, 89%); - border-color: #7da2ce; -} - -.tree-children[expanded] { - display: block; -} - -.tree-children { - display: none; -} - -.tree-item > .tree-row > * { - display: inline-block; - -webkit-box-sizing: border-box; -} - -.tree-label { - -webkit-padding-start: 20px; - background-image: url("../images/folder_closed.png"); - background-position: 0 50%; - background-repeat: no-repeat; - white-space: pre; -} - -/* We need to ensure that even empty labels take up space */ -.tree-label:empty:after { - content: " "; - white-space: pre; -} - -.tree-rename > .tree-row > .tree-label { - -webkit-user-select: auto; - -webkit-user-modify: read-write-plaintext-only; - background: white; - color: black; - outline: 1px solid black; -} - -html[dir=rtl] .tree-label { - background-position: 100% 50%; -} - -.tree-row[selected] > .tree-label { - background-image: url("../images/folder_open.png"); -} - -html[dir='rtl'] .tree-label { - background-image: url("../images/folder_closed_rtl.png"); -} - -html[dir='rtl'] .tree-row[selected] > .tree-label { - background-image: url("../images/folder_open_rtl.png"); -} - -.tree-item[editing] input { - /* Do not inherit the line-height */ - font-family: inherit; - font-size: inherit; - font-weight: inherit; - margin: -2px -8px -2px -3px; - padding: 1px 7px 1px 1px; - outline: none; -} - -html[dir=rtl] .tree-item[editing] input { - margin: -2px -3px -2px -8px; - padding: 1px 1px 1px 7px; -} - diff --git a/chrome/browser/resources/bookmark_manager/css/tree.css.js b/chrome/browser/resources/bookmark_manager/css/tree.css.js deleted file mode 100644 index a6ce38e..0000000 --- a/chrome/browser/resources/bookmark_manager/css/tree.css.js +++ /dev/null @@ -1,22 +0,0 @@ -// 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. - -(function() { - var a = 7; - var a2 = a / 2; - var ctx = document.getCSSCanvasContext('2d', 'tree-triangle', a + 1, a2 + 2); - - ctx.fillStyle = '#000'; - ctx.translate(.5, .5); - - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.lineTo(0, 1); - ctx.lineTo(a2, 1 + a2); - ctx.lineTo(a, 1); - ctx.lineTo(a, 0); - ctx.closePath(); - ctx.fill(); - ctx.stroke(); -})(); diff --git a/chrome/browser/resources/bookmark_manager/js/cr.js b/chrome/browser/resources/bookmark_manager/js/cr.js deleted file mode 100644 index 9de94da..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr.js +++ /dev/null @@ -1,314 +0,0 @@ -// 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. - -const cr = (function() { - - /** - * Whether we are using a Mac or not. - * @type {boolean} - */ - const isMac = /Mac/.test(navigator.platform); - - /** - * Builds an object structure for the provided namespace path, - * ensuring that names that already exist are not overwritten. For - * example: - * "a.b.c" -> a = {};a.b={};a.b.c={}; - * @param {string} name Name of the object that this file defines. - * @param {*=} opt_object The object to expose at the end of the path. - * @param {Object=} opt_objectToExportTo The object to add the path to; - * default is {@code window}. - * @private - */ - function exportPath(name, opt_object, opt_objectToExportTo) { - var parts = name.split('.'); - var cur = opt_objectToExportTo || window /* global */; - - for (var part; parts.length && (part = parts.shift());) { - if (!parts.length && opt_object !== undefined) { - // last part and we have an object; use it - cur[part] = opt_object; - } else if (part in cur) { - cur = cur[part]; - } else { - cur = cur[part] = {}; - } - } - return cur; - }; - - /** - * Fires a property change event on the target. - * @param {EventTarget} target The target to dispatch the event on. - * @param {string} propertyName The name of the property that changed. - * @param {*} newValue The new value for the property. - * @param {*} oldValue The old value for the property. - */ - function dispatchPropertyChange(target, propertyName, newValue, oldValue) { - // TODO(arv): Depending on cr.Event here is a bit ugly. - var e = new cr.Event(propertyName + 'Change'); - e.propertyName = propertyName; - e.newValue = newValue; - e.oldValue = oldValue; - target.dispatchEvent(e); - } - - /** - * The kind of property to define in {@code defineProperty}. - * @enum {number} - */ - const PropertyKind = { - /** - * Plain old JS property where the backing data is stored as a "private" - * field on the object. - */ - JS: 'js', - - /** - * The property backing data is stored as an attribute on an element. - */ - ATTR: 'attr', - - /** - * The property backing data is stored as an attribute on an element. If the - * element has the attribute then the value is true. - */ - BOOL_ATTR: 'boolAttr' - }; - - /** - * Helper function for defineProperty that returns the getter to use for the - * property. - * @param {string} name - * @param {cr.PropertyKind} kind - * @param {*} defaultValue The default value. This is only used for the ATTR - * kind. - * @return {function():*} The getter for the property. - */ - function getGetter(name, kind, defaultValue) { - switch (kind) { - case PropertyKind.JS: - var privateName = name + '_'; - return function() { - return this[privateName]; - }; - case PropertyKind.ATTR: - // For attr with default value we return the default value if the - // element is missing the attribute. - if (defaultValue == undefined) { - return function() { - return this.getAttribute(name); - }; - } else { - return function() { - // WebKit uses null for non existant attributes. - var value = this.getAttribute(name); - return value !== null ? value : defaultValue; - }; - } - case PropertyKind.BOOL_ATTR: - // Boolean attributes don't support default values. - return function() { - return this.hasAttribute(name); - }; - } - } - - /** - * Helper function for defineProperty that returns the setter of the right - * kind. - * @param {string} name The name of the property we are defining the setter - * for. - * @param {cr.PropertyKind} kind The kind of property we are getting the - * setter for. - * @return {function(*):void} The function to use as a setter. - */ - function getSetter(name, kind) { - switch (kind) { - case PropertyKind.JS: - var privateName = name + '_'; - return function(value) { - var oldValue = this[privateName]; - if (value !== oldValue) { - this[privateName] = value; - dispatchPropertyChange(this, name, value, oldValue); - } - }; - - case PropertyKind.ATTR: - return function(value) { - var oldValue = this[name]; - if (value !== oldValue) { - this.setAttribute(name, value); - dispatchPropertyChange(this, name, value, oldValue); - } - }; - - case PropertyKind.BOOL_ATTR: - return function(value) { - var oldValue = this[name]; - if (value !== oldValue) { - if (value) - this.setAttribute(name, name); - else - this.removeAttribute(name); - dispatchPropertyChange(this, name, value, oldValue); - } - }; - } - } - - /** - * Defines a property on an object. When the setter changes the value a - * property change event with the type {@code name + 'Change'} is fired. - * @param {!Object} The object to define the property for. - * @param {string} The name of the property. - * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use. - * @param {*} opt_defaultValue The default value. - */ - function defineProperty(obj, name, opt_kind, opt_default) { - if (typeof obj == 'function') - obj = obj.prototype; - - var kind = opt_kind || PropertyKind.JS; - - if (!obj.__lookupGetter__(name)) { - // For js properties we set the default value on the prototype. - if (kind == PropertyKind.JS && arguments.length > 3) { - var privateName = name + '_'; - obj[privateName] = opt_default; - } - obj.__defineGetter__(name, getGetter(name, kind, opt_default)); - } - - if (!obj.__lookupSetter__(name)) { - obj.__defineSetter__(name, getSetter(name, kind)); - } - } - - /** - * Counter for use with createUid - */ - var uidCounter = 1; - - /** - * @return {number} A new unique ID. - */ - function createUid() { - return uidCounter++; - } - - /** - * Returns a unique ID for the item. This mutates the item so it needs to be - * an object - * @param {!Object} item The item to get the unique ID for. - * @return {number} The unique ID for the item. - */ - function getUid(item) { - if (item.hasOwnProperty('uid')) - return item.uid; - return item.uid = createUid(); - } - - /** - * Partially applies this function to a particular 'this object' and zero or - * more arguments. The result is a new function with some arguments of the - * first function pre-filled and the value of |this| 'pre-specified'. - * - * Remaining arguments specified at call-time are appended to the pre- - * specified ones. - * - * Usage: - *
var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
-   * barMethBound('arg3', 'arg4');
- * - * @param {Function} fn A function to partially apply. - * @param {Object|undefined} selfObj Specifies the object which |this| should - * point to when the function is run. If the value is null or undefined, - * it will default to the global object. - * @param {...*} var_args Additional arguments that are partially - * applied to the function. - * - * @return {!Function} A partially-applied form of the function bind() was - * invoked as a method of. - */ - function bind(fn, selfObj, var_args) { - var boundArgs = Array.prototype.slice.call(arguments, 2); - return function() { - var args = Array.prototype.slice.call(arguments); - args.unshift.apply(args, boundArgs); - return fn.apply(selfObj, args); - } - } - - /** - * Dispatches a simple event on an event target. - * @param {!EventTarget} target The event target to dispatch the event on. - * @param {string} type The type of the event. - * @param {boolean=} opt_bubbles Whether the event bubbles or not. - * @param {boolean=} opt_cancelable Whether the default action of the event - * can be prevented. - * @return {boolean} If any of the listeners called {@code preventDefault} - * during the dispatch this will return false. - */ - function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { - var e = new cr.Event(type, opt_bubbles, opt_cancelable); - return target.dispatchEvent(e); - } - - /** - * @param {string} name - * @param {!Function} fun - */ - function define(name, fun) { - var obj = exportPath(name); - var exports = fun(); - for (var key in exports) { - obj[key] = exports[key]; - } - } - - /** - * Document used for various document related operations. - * @type {!Document} - */ - var doc = document; - - - /** - * Allows you to run func in the context of a different document. - * @param {!Document} document The document to use. - * @param {function():*} func The function to call. - */ - function withDoc(document, func) { - var oldDoc = doc; - doc = document; - try { - func(); - } finally { - doc = oldDoc; - } - } - - return { - isMac: isMac, - define: define, - defineProperty: defineProperty, - PropertyKind: PropertyKind, - createUid: createUid, - getUid: getUid, - bind: bind, - dispatchSimpleEvent: dispatchSimpleEvent, - dispatchPropertyChange: dispatchPropertyChange, - - /** - * The document that we are currently using. - * @type {!Document} - */ - get doc() { - return doc; - }, - withDoc: withDoc - }; -})(); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/event.js b/chrome/browser/resources/bookmark_manager/js/cr/event.js deleted file mode 100644 index a02a1f3..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/event.js +++ /dev/null @@ -1,43 +0,0 @@ -// 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 provides a nicer way to create events than what DOM - * provides. These events can be used with DOM EventTarget interfaces as well - * as with {@code cr.EventTarget}. - */ - -cr.define('cr', function() { - - // cr.Event is called CrEvent in here to prevent naming conflicts. We also - // store the original Event in case someone does a global alias of cr.Event. - - const DomEvent = Event; - - /** - * Creates a new event to be used with cr.EventTarget or DOM EventTarget - * objects. - * @param {string} type The name of the event. - * @param {boolean=} opt_bubbles Whether the event bubbles. Default is false. - * @param {boolean=} opt_preventable Whether the default action of the event - * can be prevented. - * @constructor - * @extends {DomEvent} - */ - function CrEvent(type, opt_bubbles, opt_preventable) { - var e = cr.doc.createEvent('Event'); - e.initEvent(type, !!opt_bubbles, !!opt_preventable); - e.__proto__ = CrEvent.prototype; - return e; - } - - CrEvent.prototype = { - __proto__: DomEvent.prototype - }; - - // Export - return { - Event: CrEvent - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/eventtarget.js b/chrome/browser/resources/bookmark_manager/js/cr/eventtarget.js deleted file mode 100644 index 5bcb41d..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/eventtarget.js +++ /dev/null @@ -1,104 +0,0 @@ -// 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 contains an implementation of the EventTarget interface - * as defined by DOM Level 2 Events. - */ - -cr.define('cr', function() { - - /** - * Creates a new EventTarget. This class implements the DOM level 2 - * EventTarget interface and can be used wherever those are used. - * @constructor - */ - function EventTarget() { - } - - EventTarget.prototype = { - - /** - * Adds an event listener to the target. - * @param {string} type The name of the event. - * @param {!Function|{handleEvent:Function}} handler The handler for the - * event. This is called when the event is dispatched. - */ - addEventListener: function(type, handler) { - if (!this.listeners_) - this.listeners_ = Object.create(null); - if (!(type in this.listeners_)) { - this.listeners_[type] = [handler]; - } else { - var handlers = this.listeners_[type]; - if (handlers.indexOf(handler) < 0) - handlers.push(handler); - } - }, - - /** - * Removes an event listener from the target. - * @param {string} type The name of the event. - * @param {!Function|{handleEvent:Function}} handler The handler for the - * event. - */ - removeEventListener: function(type, handler) { - if (!this.listeners_) - return; - if (type in this.listeners_) { - var handlers = this.listeners_[type]; - var index = handlers.indexOf(handler); - if (index >= 0) { - // Clean up if this was the last listener. - if (handlers.length == 1) - delete this.listeners_[type]; - else - handlers.splice(index, 1); - } - } - }, - - /** - * Dispatches an event and calls all the listeners that are listening to - * the type of the event. - * @param {!cr.event.Event} event The event to dispatch. - * @return {boolean} Whether the default action was prevented. If someone - * calls preventDefault on the event object then this returns false. - */ - dispatchEvent: function(event) { - if (!this.listeners_) - return true; - - // Since we are using DOM Event objects we need to override some of the - // properties and methods so that we can emulate this correctly. - var self = this; - event.__defineGetter__('target', function() { - return self; - }); - event.preventDefault = function() { - this.returnValue = false; - }; - - var type = event.type; - var prevented = 0; - if (type in this.listeners_) { - // Clone to prevent removal during dispatch - var handlers = this.listeners_[type].concat(); - for (var i = 0, handler; handler = handlers[i]; i++) { - if (handler.handleEvent) - prevented |= handler.handleEvent.call(handler, event) === false; - else - prevented |= handler.call(this, event) === false; - } - } - - return !prevented && event.returnValue; - } - }; - - // Export - return { - EventTarget: EventTarget - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/eventtarget_test.html b/chrome/browser/resources/bookmark_manager/js/cr/eventtarget_test.html deleted file mode 100644 index 998e7f1..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/eventtarget_test.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - diff --git a/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller.js b/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller.js deleted file mode 100644 index e6241de..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller.js +++ /dev/null @@ -1,168 +0,0 @@ -// 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.} 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 deleted file mode 100644 index eb7ebd5..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/linkcontroller_test.html +++ /dev/null @@ -1,353 +0,0 @@ - - - - - - - - - - - - - diff --git a/chrome/browser/resources/bookmark_manager/js/cr/promise.js b/chrome/browser/resources/bookmark_manager/js/cr/promise.js deleted file mode 100644 index a9d233a..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/promise.js +++ /dev/null @@ -1,213 +0,0 @@ -// 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 implementes a future promise class. - */ - -cr.define('cr', function() { - - /** - * Sentinel used to mark a value as pending. - */ - const PENDING_VALUE = {}; - - /** - * Creates a future promise. - * @param {*=} opt_value The value to set the promise to. If set completes - * the promise immediately. - * @constructor - */ - function Promise(opt_value) { - /** - * An array of the callbacks. - * @type {!Array.} - * @private - */ - this.callbacks_ = []; - - if (arguments.length > 0) - this.value = opt_value; - } - - Promise.prototype = { - /** - * The current value. - * @type {*} - * @private - */ - value_: PENDING_VALUE, - - /** - * The value of the future promise. Accessing this before the promise has - * been fulfilled will throw an error. If this is set to an exception - * accessing this will throw as well. - * @type {*} - */ - get value() { - return this.done ? this.value_ : undefined; - }, - set value(value) { - if (!this.done) { - this.value_ = value; - for (var i = 0; i < this.callbacks_.length; i++) { - this.callbacks_[i].call(null, value); - } - this.callbacks_.length = 0; - } - }, - - /** - * Whether the future promise has been fulfilled. - * @type {boolean} - */ - get done() { - return this.value_ !== PENDING_VALUE; - }, - - /** - * Adds a listener to the future promise. The function will be called when - * the promise is fulfilled. If the promise is already fullfilled this will - * never call the function. - * @param {!Function} fun The function to call. - */ - addListener: function(fun) { - if (this.done) - fun(this.value); - else - this.callbacks_.push(fun); - }, - - /** - * Removes a previously added listener from the future promise. - * @param {!Function} fun The function to remove. - */ - removeListener: function(fun) { - var i = this.callbacks_.indexOf(fun); - if (i >= 0) - this.callbacks_.splice(i, 1); - }, - - /** - * If the promise is done then this returns the string representation of - * the value. - * @return {string} The string representation of the promise. - * @override - */ - toString: function() { - if (this.done) - return String(this.value); - else - return '[object Promise]'; - }, - - /** - * Override to allow arithmetic. - * @override - */ - valueOf: function() { - return this.value; - } - }; - - /** - * When a future promise is done call {@code fun}. This also calls the - * function if the promise has already been fulfilled. - * @param {!Promise} p The promise. - * @param {!Function} fun The function to call when the promise is fulfilled. - */ - Promise.when = function(p, fun) { - p.addListener(fun); - }; - - /** - * Creates a new promise the will be fulfilled after {@code t} ms. - * @param {number} t The time to wait before the promise is fulfilled. - * @param {*=} opt_value The value to return after the wait. - * @return {!Promise} The new future promise. - */ - Promise.wait = function(t, opt_value) { - var p = new Promise; - window.setTimeout(function() { - p.value = opt_value; - }, t); - return p; - }; - - /** - * Creates a new future promise that is fulfilled when any of the promises are - * 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 the - * passed in promises are fulfilled. - */ - Promise.any = function(var_args) { - var p = new Promise; - function f(v) { - p.value = v; - } - for (var i = 0; i < arguments.length; i++) { - arguments[i].addListener(f); - } - return p; - }; - - /** - * 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. - * @param {boolean=} opt_useCapture Whether to listen to the capture phase or - * the bubble phase. - * @return {!Promise} The promise that will be fulfilled when the event is - * dispatched. - */ - Promise.event = function(target, type, opt_useCapture) { - var p = new Promise; - target.addEventListener(type, function(e) { - p.value = e; - }, opt_useCapture); - return p; - }; - - return { - Promise: Promise - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/promise_test.html b/chrome/browser/resources/bookmark_manager/js/cr/promise_test.html deleted file mode 100644 index 5c9ae1d..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/promise_test.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - - diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui.js b/chrome/browser/resources/bookmark_manager/js/cr/ui.js deleted file mode 100644 index d87c04a..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui.js +++ /dev/null @@ -1,173 +0,0 @@ -// 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. - -cr.define('cr.ui', function() { - - /** - * Decorates elements as an instance of a class. - * @param {string|!Element} source The way to find the element(s) to decorate. - * If this is a string then {@code querySeletorAll} is used to find the - * elements to decorate. - * @param {!Function} constr The constructor to decorate with. The constr - * needs to have a {@code decorate} function. - */ - function decorate(source, constr) { - var elements; - if (typeof source == 'string') - elements = cr.doc.querySelectorAll(source); - else - elements = [source]; - - for (var i = 0, el; el = elements[i]; i++) { - if (!(el instanceof constr)) - constr.decorate(el); - } - } - - /** - * Helper function for creating new element for define. - */ - function createElementHelper(tagName, opt_bag) { - // Allow passing in ownerDocument to create in a different document. - var doc; - if (opt_bag && opt_bag.ownerDocument) - doc = opt_bag.ownerDocument; - else - doc = cr.doc; - return doc.createElement(tagName); - } - - /** - * Creates the constructor for a UI element class. - * - * Usage: - *
-   * var List = cr.ui.define('list');
-   * List.prototype = {
-   *   __proto__: HTMLUListElement.prototype,
-   *   decorate: function() {
-   *     ...
-   *   },
-   *   ...
-   * };
-   * 
- * - * @param {string|Function} tagNameOrFunction The tagName or - * function to use for newly created elements. If this is a function it - * needs to return a new element when called. - * @return {function(Object=):Element} The constructor function which takes - * an optional property bag. The function also has a static - * {@code decorate} method added to it. - */ - function define(tagNameOrFunction) { - var createFunction, tagName; - if (typeof tagNameOrFunction == 'function') { - createFunction = tagNameOrFunction; - tagName = ''; - } else { - createFunction = createElementHelper; - tagName = tagNameOrFunction; - } - - /** - * Creates a new UI element constructor. - * @param {Object=} opt_propertyBag Optional bag of properties to set on the - * object after created. The property {@code ownerDocument} is special - * cased and it allows you to create the element in a different - * document than the default. - * @constructor - */ - function f(opt_propertyBag) { - var el = createFunction(tagName, opt_propertyBag); - f.decorate(el); - for (var propertyName in opt_propertyBag) { - el[propertyName] = opt_propertyBag[propertyName]; - } - return el; - } - - /** - * Decorates an element as a UI element class. - * @param {!Element} el The element to decorate. - */ - f.decorate = function(el) { - el.__proto__ = f.prototype; - el.decorate(); - }; - - return f; - } - - /** - * Input elements do not grow and shrink with their content. This is a simple - * (and not very efficient) way of handling shrinking to content with support - * for min width and limited by the width of the parent element. - * @param {HTMLElement} el The element to limit the width for. - * @param {number} parentEl The parent element that should limit the size. - * @param {number} min The minimum width. - */ - function limitInputWidth(el, parentEl, min) { - // Needs a size larger than borders - el.style.width = '10px'; - var doc = el.ownerDocument; - var win = doc.defaultView; - var computedStyle = win.getComputedStyle(el); - var parentComputedStyle = win.getComputedStyle(parentEl); - var rtl = computedStyle.direction == 'rtl'; - - // To get the max width we get the width of the treeItem minus the position - // of the input. - var inputRect = el.getBoundingClientRect(); // box-sizing - var parentRect = parentEl.getBoundingClientRect(); - var startPos = rtl ? parentRect.right - inputRect.right : - inputRect.left - parentRect.left; - - // Add up border and padding of the input. - var inner = parseInt(computedStyle.borderLeftWidth, 10) + - parseInt(computedStyle.paddingLeft, 10) + - parseInt(computedStyle.paddingRight, 10) + - parseInt(computedStyle.borderRightWidth, 10); - - // We also need to subtract the padding of parent to prevent it to overflow. - var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : - parseInt(parentComputedStyle.paddingRight, 10); - - // The magic number 14 comes from trial and error :'( It consists of: - // border + padding + treeItem.paddingEnd + treeItem.borderEnd + - // tree.paddingEnd - var max = parentEl.clientWidth - startPos - inner - parentPadding; - - var pcs = getComputedStyle(parentEl); - console.log('pcs', 'borderLeft', pcs.borderLeftWidth, - 'paddingLeft', pcs.paddingLeft, - 'paddingRight', pcs.paddingRight, - 'borderRight', pcs.borderRightWidth, - 'width', pcs.width, - 'clientWidth', parentEl.clientWidth, - 'offsetWidth', parentEl.offsetWidth); - - function limit() { - if (el.scrollWidth > max) { - el.style.width = max + 'px'; - } else { - el.style.width = 0; - var sw = el.scrollWidth; - if (sw < min) { - el.style.width = min + 'px'; - } else { - el.style.width = sw + 'px'; - } - } - } - - el.addEventListener('input', limit); - limit(); - } - - return { - decorate: decorate, - define: define, - limitInputWidth: limitInputWidth - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/command.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/command.js deleted file mode 100644 index 4df2275..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/command.js +++ /dev/null @@ -1,295 +0,0 @@ -// 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 A command is an abstraction of an action a user can do in the - * UI. - * - * When the focus changes in the document for each command a canExecute event - * is dispatched on the active element. By listening to this event you can - * enable and disable the command by setting the event.canExecute property. - * - * When a command is executed a command event is dispatched on the active - * element. Note that you should stop the propagation after you have handled the - * command if there might be other command listeners higher up in the DOM tree. - */ - -cr.define('cr.ui', function() { - - /** - * This is used to identify keyboard shortcuts. - * @param {string} shortcut The text used to describe the keys for this - * keyboard shortcut. - * @constructor - */ - function KeyboardShortcut(shortcut) { - var mods = {}; - var ident = ''; - shortcut.split('-').forEach(function(part) { - var partLc = part.toLowerCase(); - switch (partLc) { - case 'alt': - case 'ctrl': - case 'meta': - case 'shift': - mods[partLc + 'Key'] = true; - break; - default: - if (ident) - throw Error('Invalid shortcut'); - ident = part; - } - }); - - this.ident_ = ident; - this.mods_ = mods; - } - - KeyboardShortcut.prototype = { - /** - * Wether the keyboard shortcut object mathes a keyboard event. - * @param {!Event} e The keyboard event object. - * @return {boolean} Whether we found a match or not. - */ - matchesEvent: function(e) { - if (e.keyIdentifier == this.ident_) { - // All keyboard modifiers needs to match. - var mods = this.mods_; - return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) { - return e[k] == !!mods[k]; - }); - } - return false; - } - }; - - /** - * Creates a new command element. - * @constructor - * @extends {HTMLElement} - */ - var Command = cr.ui.define('command'); - - Command.prototype = { - __proto__: HTMLElement.prototype, - - /** - * Initializes the command. - */ - decorate: function() { - CommandManager.init(this.ownerDocument); - }, - - /** - * Executes the command. This dispatches a command event on the active - * element. If the command is {@code disabled} this does nothing. - */ - execute: function() { - if (this.disabled) - return; - var doc = this.ownerDocument; - if (doc.activeElement) { - var e = new cr.Event('command', true, false); - e.command = this; - doc.activeElement.dispatchEvent(e); - } - }, - - /** - * Call this when there have been changes that might change whether the - * command can be executed or not. - */ - canExecuteChange: function() { - dispatchCanExecuteEvent(this, this.ownerDocument.activeElement); - }, - - /** - * The keyboard shortcut that triggers the command. This is a string - * consisting of a keyIdentifier (as reported by WebKit in keydown) as - * well as optional key modifiers joinded with a '-'. - * - * Multiple keyboard shortcuts can be provided by separating them by - * whitespace. - * - * For example: - * "F1" - * "U+0008-Meta" for Apple command backspace. - * "U+0041-Ctrl" for Control A - * "U+007F U+0008-Meta" for Delete and Command Backspace - * - * @type {string} - */ - shortcut_: '', - get shortcut() { - return this.shortcut_; - }, - set shortcut(shortcut) { - var oldShortcut = this.shortcut_; - if (shortcut !== oldShortcut) { - this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) { - return new KeyboardShortcut(shortcut); - }); - - // Set this after the keyboardShortcuts_ since that might throw. - this.shortcut_ = shortcut; - cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_, - oldShortcut); - } - }, - - /** - * Whether the event object matches the shortcut for this command. - * @param {!Event} e The key event object. - * @return {boolean} Whether it matched or not. - */ - matchesEvent: function(e) { - if (!this.keyboardShortcuts_) - return false; - - return this.keyboardShortcuts_.some(function(keyboardShortcut) { - return keyboardShortcut.matchesEvent(e); - }); - } - }; - - /** - * The label of the command. - * @type {string} - */ - cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR); - - /** - * Whether the command is disabled or not. - * @type {boolean} - */ - cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR); - - /** - * Whether the command is hidden or not. - * @type {boolean} - */ - cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR); - - /** - * Dispatches a canExecute event on the target. - * @param {cr.ui.Command} command The command that we are testing for. - * @param {Element} target The target element to dispatch the event on. - */ - function dispatchCanExecuteEvent(command, target) { - var e = new CanExecuteEvent(command, true) - target.dispatchEvent(e); - command.disabled = !e.canExecute; - } - - /** - * The command managers for different documents. - */ - var commandManagers = {}; - - /** - * Keeps track of the focused element and updates the commands when the focus - * changes. - * @param {!Document} doc The document that we are managing the commands for. - * @constructor - */ - function CommandManager(doc) { - doc.addEventListener('focus', cr.bind(this.handleFocus_, this), true); - // Make sure we add the listener to the bubbling phase so that elements can - // prevent the command. - doc.addEventListener('keydown', cr.bind(this.handleKeyDown_, this), false); - } - - /** - * Initializes a command manager for the document as needed. - * @param {!Document} doc The document to manage the commands for. - */ - CommandManager.init = function(doc) { - var uid = cr.getUid(doc); - if (!(uid in commandManagers)) { - commandManagers[uid] = new CommandManager(doc); - } - }, - - CommandManager.prototype = { - - /** - * Handles focus changes on the document. - * @param {Event} e The focus event object. - * @private - */ - handleFocus_: function(e) { - var target = e.target; - var commands = Array.prototype.slice.call( - target.ownerDocument.querySelectorAll('command')); - - commands.forEach(function(command) { - dispatchCanExecuteEvent(command, target); - }); - }, - - /** - * Handles the keydown event and routes it to the right command. - * @param {!Event} e The keydown event. - */ - handleKeyDown_: function(e) { - var target = e.target; - var commands = Array.prototype.slice.call( - target.ownerDocument.querySelectorAll('command')); - - for (var i = 0, command; command = commands[i]; i++) { - if (!command.disabled && command.matchesEvent(e)) { - e.preventDefault(); - // We do not want any other element to handle this. - e.stopPropagation(); - - command.execute(); - return; - } - } - } - }; - - /** - * The event type used for canExecute events. - * @param {!cr.ui.Command} command The command that we are evaluating. - * @extends {Event} - */ - function CanExecuteEvent(command) { - var e = command.ownerDocument.createEvent('Event'); - e.initEvent('canExecute', true, false); - e.__proto__ = CanExecuteEvent.prototype; - e.command = command; - return e; - } - - CanExecuteEvent.prototype = { - __proto__: Event.prototype, - - /** - * The current command - * @type {cr.ui.Command} - */ - command: null, - - /** - * Whether the target can execute the command. Setting this also stops the - * propagation. - * @type {boolean} - */ - canExecute_: false, - get canExecute() { - return this.canExecute_; - }, - set canExecute(canExecute) { - this.canExecute_ = canExecute; - this.stopPropagation(); - } - }; - - // Export - return { - Command: Command, - CanExecuteEvent: CanExecuteEvent - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/contextmenuhandler.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/contextmenuhandler.js deleted file mode 100644 index 2d96a4c..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/contextmenuhandler.js +++ /dev/null @@ -1,225 +0,0 @@ -// 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. - -cr.define('cr.ui', function() { - - /** - * Handles context menus. - * @constructor - */ - function ContextMenuHandler() {} - - ContextMenuHandler.prototype = { - - /** - * The menu that we are currently showing. - * @type {cr.ui.Menu} - */ - menu_: null, - get menu() { - return this.menu_; - }, - - /** - * Shows a menu as a context menu. - * @param {!Event} e The event triggering the show (usally a contextmenu - * event). - * @param {!cr.ui.Menu} menu The menu to show. - */ - showMenu: function(e, menu) { - this.menu_ = menu; - - menu.style.display = 'block'; - // when the menu is shown we steal all keyboard events. - menu.ownerDocument.addEventListener('keydown', this, true); - menu.ownerDocument.addEventListener('mousedown', this, true); - menu.ownerDocument.addEventListener('blur', this, true); - menu.addEventListener('activate', this); - this.positionMenu_(e, menu); - }, - - /** - * Hide the currently shown menu. - */ - hideMenu: function() { - var menu = this.menu; - if (!menu) - return; - - menu.style.display = 'none'; - menu.ownerDocument.removeEventListener('keydown', this, true); - menu.ownerDocument.removeEventListener('mousedown', this, true); - menu.ownerDocument.removeEventListener('blur', this, true); - menu.removeEventListener('activate', this); - menu.selectedIndex = -1; - this.menu_ = null; - - // On windows we might hide the menu in a right mouse button up and if - // that is the case we wait some short period before we allow the menu - // to be shown again. - this.hideTimestamp_ = Date.now(); - }, - - /** - * Positions the menu - * @param {!Event} e The event object triggering the showing. - * @param {!cr.ui.Menu} menu The menu to position. - * @private - */ - positionMenu_: function(e, menu) { - // TODO(arv): Handle scrolled documents when needed. - - var element = e.currentTarget; - var x, y; - // When the user presses the context menu key (on the keyboard) we need - // to detect this. - if (e.screenX == 0 && e.screenY == 0) { - var rect = element.getRectForContextMenu ? - element.getRectForContextMenu() : - element.getBoundingClientRect(); - var offset = Math.min(rect.width, rect.height) / 2; - x = rect.left + offset; - y = rect.top + offset; - } else { - x = e.clientX; - y = e.clientY; - } - - var menuRect = menu.getBoundingClientRect(); - var bodyRect = menu.ownerDocument.body.getBoundingClientRect(); - - // Does menu fit below? - if (y + menuRect.height > bodyRect.height) { - // Does menu fit above? - if (y - menuRect.height >= 0) { - y -= menuRect.height; - } else { - // Menu did not fit above nor below. - y = 0; - // We could resize the menu here but lets not worry about that at this - // point. - } - } - - // Does menu fit to the right? - if (x + menuRect.width > bodyRect.width) { - // Does menu fit to the left? - if (x - menuRect.width >= 0) { - x -= menuRect.width; - } else { - // Menu did not fit to the right nor to the left. - x = 0; - // We could resize the menu here but lets not worry about that at this - // point. - } - } - - menu.style.left = x + 'px'; - menu.style.top = y + 'px'; - }, - - /** - * Handles event callbacks. - * @param {!Event} e The event object. - */ - handleEvent: function(e) { - // Context menu is handled even when we have no menu. - if (e.type != 'contextmenu' && !this.menu) - return; - - switch (e.type) { - case 'mousedown': - if (!this.menu.contains(e.target)) - this.hideMenu(); - else - e.preventDefault(); - break; - case 'keydown': - // keyIdentifier does not report 'Esc' correctly - if (e.keyCode == 27 /* Esc */) { - this.hideMenu(); - - // If the menu is visible we let it handle all the keyboard events. - } else if (this.menu) { - this.menu.handleKeyDown(e); - e.preventDefault(); - e.stopPropagation(); - } - break; - - case 'activate': - case 'blur': - this.hideMenu(); - break; - - case 'contextmenu': - if ((!this.menu || !this.menu.contains(e.target)) && - (!this.hideTimestamp_ || Date.now() - this.hideTimestamp_ > 50)) - this.showMenu(e, e.currentTarget.contextMenu); - e.preventDefault(); - // Don't allow elements further up in the DOM to show their menus. - e.stopPropagation(); - break; - } - }, - - /** - * Adds a contextMenu property to an element or element class. - * @param {!Element|!Function} element The element or class to add the - * contextMenu property to. - */ - addContextMenuProperty: function(element) { - if (typeof element == 'function') - element = element.prototype; - - element.__defineGetter__('contextMenu', function() { - return this.contextMenu_; - }); - element.__defineSetter__('contextMenu', function(menu) { - var oldContextMenu = this.contextMenu; - - if (typeof menu == 'string' && menu[0] == '#') { - menu = this.ownerDocument.getElementById(menu.slice(1)); - cr.ui.decorate(menu, Menu); - } - - if (menu === oldContextMenu) - return; - - if (oldContextMenu && !menu) - this.removeEventListener('contextmenu', contextMenuHandler); - if (menu && !oldContextMenu) - this.addEventListener('contextmenu', contextMenuHandler); - - this.contextMenu_ = menu; - - if (menu && menu.id) - this.setAttribute('contextmenu', '#' + menu.id); - - cr.dispatchPropertyChange(this, 'contextMenu', menu, oldContextMenu); - }); - - if (!element.getRectForContextMenu) { - /** - * @return {!ClientRect} The rect to use for positioning the context - * menu when the context menu is not opened using a mouse position. - */ - element.getRectForContextMenu = function() { - return this.getBoundingClientRect(); - }; - } - } - }; - - /** - * The singleton context menu handler. - * @type {!ContextMenuHandler} - */ - var contextMenuHandler = new ContextMenuHandler; - - // Export - return { - contextMenuHandler: contextMenuHandler - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/list.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/list.js deleted file mode 100644 index f221279..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/list.js +++ /dev/null @@ -1,265 +0,0 @@ -// 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. - -// require: listselectionmodel.js - -/** - * @fileoverview This implements a list control. - */ - -cr.define('cr.ui', function() { - const ListSelectionModel = cr.ui.ListSelectionModel; - - /** - * Whether a mouse event is inside the element viewport. This will return - * false if the mouseevent was generated over a border or a scrollbar. - * @param {!HTMLElement} el The element to test the event with. - * @param {!Event} e The mouse event. - * @param {boolean} Whether the mouse event was inside the viewport. - */ - function inViewport(el, e) { - var rect = el.getBoundingClientRect(); - var x = e.clientX; - var y = e.clientY; - return x >= rect.left + el.clientLeft && - x < rect.left + el.clientLeft + el.clientWidth && - y >= rect.top + el.clientTop && - y < rect.top + el.clientTop + el.clientHeight; - } - - /** - * Creates a new list element. - * @param {Object=} opt_propertyBag Optional properties. - * @constructor - * @extends {HTMLUListElement} - */ - var List = cr.ui.define('list'); - - List.prototype = { - __proto__: HTMLUListElement.prototype, - - /** - * The selection model to use. - * @type {cr.ui.ListSelectionModel} - */ - get selectionModel() { - return this.selectionModel_; - }, - set selectionModel(sm) { - var oldSm = this.selectionModel_; - if (oldSm == sm) - return; - - if (!this.boundHandleOnChange_) { - this.boundHandleOnChange_ = cr.bind(this.handleOnChange_, this); - this.boundHandleLeadChange_ = cr.bind(this.handleLeadChange_, this); - } - - if (oldSm) { - oldSm.removeEventListener('change', this.boundHandleOnChange_); - oldSm.removeEventListener('leadItemChange', this.boundHandleLeadChange_); - } - - this.selectionModel_ = sm; - - if (sm) { - sm.addEventListener('change', this.boundHandleOnChange_); - sm.addEventListener('leadItemChange', this.boundHandleLeadChange_); - } - }, - - /** - * Convenience alias for selectionModel.selectedItem - * @type {cr.ui.ListItem} - */ - get selectedItem() { - return this.selectionModel.selectedItem; - }, - set selectedItem(selectedItem) { - this.selectionModel.selectedItem = selectedItem; - }, - - /** - * Convenience alias for selectionModel.selectedItems - * @type {!Array} - */ - get selectedItems() { - return this.selectionModel.selectedItems; - }, - - /** - * The HTML elements representing the items. This is just all the element - * children but subclasses may override this to filter out certain elements. - * @type {HTMLCollection} - */ - get items() { - return this.children; - }, - - batchCount_: 0, - - /** - * When adding a large collection of items to the list, the code should be - * wrapped in the startBatchAdd and startBatchEnd to increase performance. - * This hides the list while it is being built, and prevents it from - * incurring measurement performance hits in between each item. - * Be sure that the code will not return without calling finishBatchAdd - * or the list will not be shown. - * @private - */ - startBatchAdd: function() { - // If we're already in a batch, don't overwrite original display style. - if (this.batchCount_ == 0) { - this.originalDisplayStyle_ = this.style.display; - this.style.display = 'none'; - } - this.batchCount_++; - }, - - /** - * See startBatchAdd. - * @private - */ - finishBatchAdd: function() { - this.batchCount_--; - if (this.batchCount_ == 0) { - this.style.display = this.originalDisplayStyle_; - delete this.originalDisplayStyle; - } - }, - - add: function(listItem) { - this.appendChild(listItem); - - var uid = cr.getUid(listItem); - this.uidToListItem_[uid] = listItem; - - this.selectionModel.add(listItem); - }, - - addAt: function(listItem, index) { - this.insertBefore(listItem, this.items[index]); - - var uid = cr.getUid(listItem); - this.uidToListItem_[uid] = listItem; - - this.selectionModel.add(listItem); - }, - - remove: function(listItem) { - this.selectionModel.remove(listItem); - - this.removeChild(listItem); - - var uid = cr.getUid(listItem); - delete this.uidToListItem_[uid]; - }, - - clear: function() { - this.innerHTML = ''; - this.selectionModel.clear(); - }, - - /** - * Initializes the element. - */ - decorate: function() { - this.uidToListItem_ = {}; - - this.selectionModel = new ListSelectionModel(this); - - this.addEventListener('mousedown', this.handleMouseDownUp_); - this.addEventListener('mouseup', this.handleMouseDownUp_); - this.addEventListener('keydown', this.handleKeyDown); - - // Make list focusable - if (!this.hasAttribute('tabindex')) - this.tabIndex = 0; - }, - - /** - * Callback for mousedown and mouseup events. - * @param {Event} e The mouse event object. - * @private - */ - handleMouseDownUp_: function(e) { - var target = e.target; - - // If the target was this element we need to make sure that the user did - // not click on a border or a scrollbar. - if (target == this && !inViewport(target, e)) - return; - - while (target && target.parentNode != this) { - target = target.parentNode; - } - - this.selectionModel.handleMouseDownUp(e, target); - }, - - /** - * Handle a keydown event. - * @param {Event} e The keydown event. - * @return {boolean} Whether the key event was handled. - */ - handleKeyDown: function(e) { - return this.selectionModel.handleKeyDown(e); - }, - - /** - * Callback from the selection model. We dispatch {@code change} events - * when the selection changes. - * @param {!cr.Event} e Event with change info. - * @private - */ - handleOnChange_: function(ce) { - ce.changes.forEach(function(change) { - var listItem = this.uidToListItem_[change.uid]; - listItem.selected = change.selected; - }, this); - - cr.dispatchSimpleEvent(this, 'change'); - }, - - /** - * Handles a change of the lead item from the selection model. - * @property {Event} pe The property change event. - * @private - */ - handleLeadChange_: function(pe) { - if (pe.oldValue) { - pe.oldValue.lead = false; - } - if (pe.newValue) { - pe.newValue.lead = true; - } - }, - - /** - * Gets a unique ID for an item. This needs to be unique to the list but - * does not have to be gloabally unique. This uses {@code cr.getUid} by - * default. Override to provide a more efficient way to get the unique ID. - * @param {cr.ui.ListItem} item The item to get the unique ID for. - * @return - */ - itemToUid: function(item) { - return cr.getUid(item); - }, - - /** - * @return {!ClientRect} The rect to use for the context menu. - */ - getRectForContextMenu: function() { - // TODO(arv): Add trait support so we can share more code between trees - // and lists. - if (this.selectedItem) - return this.selectedItem.getBoundingClientRect(); - return this.getBoundingClientRect(); - } - }; - - return { - List: List - } -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/listitem.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/listitem.js deleted file mode 100644 index 0cd8826..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/listitem.js +++ /dev/null @@ -1,58 +0,0 @@ -// 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. - -cr.define('cr.ui', function() { - - /** - * Creates a new list item element. - * @param {string} opt_label The text label for the item. - * @constructor - * @extends {HTMLLIElement} - */ - var ListItem = cr.ui.define('li'); - - ListItem.prototype = { - __proto__: HTMLLIElement.prototype, - - /** - * Plain text label. - * @type {string} - */ - get label() { - return this.textContent; - }, - set label(label) { - this.textContent = label; - }, - - /** - * Whether the item is the lead in a selection. Setting this does not update - * the underlying selection model. This is only used for display purpose. - * @type {boolean} - */ - get lead() { - return this.hasAttribute('lead'); - }, - set lead(lead) { - if (lead) { - this.setAttribute('lead', ''); - this.scrollIntoViewIfNeeded(false); - } else { - this.removeAttribute('lead'); - } - }, - - /** - * Called when an element is decorated as a list item. - */ - decorate: function() { - } - }; - - cr.defineProperty(ListItem, 'selected', cr.PropertyKind.BOOL_ATTR); - - return { - ListItem: ListItem - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/listselectionmodel.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/listselectionmodel.js deleted file mode 100644 index ef9bce8..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/listselectionmodel.js +++ /dev/null @@ -1,450 +0,0 @@ -// 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. - -cr.define('cr.ui', function() { - const Event = cr.Event; - const EventTarget = cr.EventTarget; - - /** - * Creates a new selection model that is to be used with lists. This is - * implemented for vertical lists but changing the behavior for horizontal - * lists or icon views is a matter of overriding {@code getItemBefore}, - * {@code getItemAfter}, {@code getItemAbove} as well as {@code getItemBelow}. - * - * @constructor - * @extends {!cr.EventTarget} - */ - function ListSelectionModel(list) { - this.list = list; - this.selectedItems_ = {}; - } - - ListSelectionModel.prototype = { - __proto__: EventTarget.prototype, - - /** - * Returns the item below (y axis) the given element. - * @param {*} item The item to get the item below. - * @return {*} The item below or null if not found. - */ - getItemBelow: function(item) { - return item.nextElementSibling; - }, - - /** - * Returns the item above (y axis) the given element. - * @param {*} item The item to get the item above. - * @return {*} The item below or null if not found. - */ - getItemAbove: function(item) { - return item.previousElementSibling; - }, - - /** - * Returns the item before (x axis) the given element. This returns null - * by default but override this for icon view and horizontal selection - * models. - * - * @param {*} item The item to get the item before. - * @return {*} The item before or null if not found. - */ - getItemBefore: function(item) { - return null; - }, - - /** - * Returns the item after (x axis) the given element. This returns null - * by default but override this for icon view and horizontal selection - * models. - * - * @param {*} item The item to get the item after. - * @return {*} The item after or null if not found. - */ - getItemAfter: function(item) { - return null; - }, - - /** - * Returns the next list item. This is the next logical and should not - * depend on any kind of layout of the list. - * @param {*} item The item to get the next item for. - * @return {*} The next item or null if not found. - */ - getNextItem: function(item) { - return item.nextElementSibling; - }, - - /** - * Returns the prevous list item. This is the previous logical and should - * not depend on any kind of layout of the list. - * @param {*} item The item to get the previous item for. - * @return {*} The previous item or null if not found. - */ - getPreviousItem: function(item) { - return item.previousElementSibling; - }, - - /** - * @return {*} The first item. - */ - getFirstItem: function() { - return this.list.firstElementChild; - }, - - /** - * @return {*} The last item. - */ - getLastItem: function() { - return this.list.lastElementChild; - }, - - /** - * Called by the view when the user does a mousedown or mouseup on the list. - * @param {!Event} e The browser mousedown event. - * @param {*} item The item that was under the mouse pointer, null if none. - */ - handleMouseDownUp: function(e, item) { - var anchorItem = this.anchorItem; - var isDown = e.type == 'mousedown'; - - this.beginChange_(); - - if (!item) { - // On Mac we always clear the selection if the user clicks a blank area. - // On Windows, we only clear the selection if neither Shift nor Ctrl are - // pressed. - if (cr.isMac) { - this.clear(); - } else if (!isDown && !e.shiftKey && !e.ctrlKey) - // Keep anchor and lead items. - this.clearAllSelected_(); - } else { - if (cr.isMac ? e.metaKey : e.ctrlKey) { - // Selection is handled at mouseUp on windows/linux, mouseDown on mac. - if (cr.isMac? isDown : !isDown) { - // toggle the current one and make it anchor item - this.setItemSelected(item, !this.getItemSelected(item)); - this.leadItem = item; - this.anchorItem = item; - } - } else if (e.shiftKey && anchorItem && anchorItem != item) { - // Shift is done in mousedown - if (isDown) { - this.clearAllSelected_(); - this.leadItem = item; - this.selectRange(anchorItem, item); - } - } else { - // Right click for a context menu need to not clear the selection. - var isRightClick = e.button == 2; - - // If the item is selected this is handled in mouseup. - var itemSelected = this.getItemSelected(item); - if ((itemSelected && !isDown || !itemSelected && isDown) && - !(itemSelected && isRightClick)) { - this.clearAllSelected_(); - this.setItemSelected(item, true); - this.leadItem = item; - this.anchorItem = item; - } - } - } - - this.endChange_(); - }, - - /** - * Called by the view when it recieves a keydown event. - * @param {Event} e The keydown event. - */ - handleKeyDown: function(e) { - var newItem = null; - var leadItem = this.leadItem; - var prevent = true; - - // Ctrl/Meta+A - if (e.keyCode == 65 && - (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) { - this.selectAll(); - e.preventDefault(); - return; - } - - // Space - if (e.keyCode == 32) { - if (leadItem != null) { - var selected = this.getItemSelected(leadItem); - if (e.ctrlKey || !selected) { - this.beginChange_(); - this.setItemSelected(leadItem, !selected); - this.endChange_(); - return; - } - } - } - - switch (e.keyIdentifier) { - case 'Home': - newItem = this.getFirstItem(); - break; - case 'End': - newItem = this.getLastItem(); - break; - case 'Up': - newItem = !leadItem ? - this.getLastItem() : this.getItemAbove(leadItem); - break; - case 'Down': - newItem = !leadItem ? - this.getFirstItem() : this.getItemBelow(leadItem); - break; - case 'Left': - newItem = !leadItem ? - this.getLastItem() : this.getItemBefore(leadItem); - break; - case 'Right': - newItem = !leadItem ? - this.getFirstItem() : this.getItemAfter(leadItem); - break; - default: - prevent = false; - } - - if (newItem) { - this.beginChange_(); - - this.leadItem = newItem; - if (e.shiftKey) { - var anchorItem = this.anchorItem; - this.clearAllSelected_(); - if (!anchorItem) { - this.setItemSelected(newItem, true); - this.anchorItem = newItem; - } else { - this.selectRange(anchorItem, newItem); - } - } else if (e.ctrlKey && !cr.isMac) { - // Setting the lead item is done above - // Mac does not allow you to change the lead. - } else { - this.clearAllSelected_(); - this.setItemSelected(newItem, true); - this.anchorItem = newItem; - } - - this.endChange_(); - - if (prevent) - e.preventDefault(); - } - }, - - /** - * @type {!Array} The selected items. - */ - get selectedItems() { - return Object.keys(this.selectedItems_).map(function(uid) { - return this.selectedItems_[uid]; - }, this); - }, - set selectedItems(selectedItems) { - this.beginChange_(); - this.clearAllSelected_(); - for (var i = 0; i < selectedItems.length; i++) { - this.setItemSelected(selectedItems[i], true); - } - this.leadItem = this.anchorItem = selectedItems[0] || null; - this.endChange_(); - }, - - /** - * Convenience getter which returns the first selected item. - * @type {*} - */ - get selectedItem() { - for (var uid in this.selectedItems_) { - return this.selectedItems_[uid]; - } - return null; - }, - set selectedItem(selectedItem) { - this.beginChange_(); - this.clearAllSelected_(); - if (selectedItem) { - this.selectedItems = [selectedItem]; - } else { - this.leadItem = this.anchorItem = null; - } - this.endChange_(); - }, - - /** - * Selects a range of items, starting with {@code start} and ends with - * {@code end}. - * @param {*} start The first item to select. - * @param {*} end The last item to select. - */ - selectRange: function(start, end) { - // Swap if starts comes after end. - if (start.compareDocumentPosition(end) & Node.DOCUMENT_POSITION_PRECEDING) { - var tmp = start; - start = end; - end = tmp; - } - - this.beginChange_(); - - for (var item = start; item != end; item = this.getNextItem(item)) { - this.setItemSelected(item, true); - } - this.setItemSelected(end, true); - - this.endChange_(); - }, - - /** - * Selects all items. - */ - selectAll: function() { - this.selectRange(this.getFirstItem(), this.getLastItem()); - }, - - /** - * Clears the selection - */ - clear: function() { - this.beginChange_(); - this.anchorItem = this.leadItem = null; - this.clearAllSelected_(); - this.endChange_(); - }, - - /** - * Clears the selection and updates the view. - * @private - */ - clearAllSelected_: function() { - for (var uid in this.selectedItems_) { - this.setItemSelected(this.selectedItems_[uid], false); - } - }, - - /** - * Sets the selecte state for an item. - * @param {*} item The item to set the selected state for. - * @param {boolean} b Whether to select the item or not. - */ - setItemSelected: function(item, b) { - var uid = this.list.itemToUid(item); - var oldSelected = uid in this.selectedItems_; - if (oldSelected == b) - return; - - if (b) - this.selectedItems_[uid] = item; - else - delete this.selectedItems_[uid]; - - this.beginChange_(); - - // Changing back? - if (uid in this.changedUids_ && this.changedUids_[uid] == !b) { - delete this.changedUids_[uid]; - } else { - this.changedUids_[uid] = b; - } - - // End change dispatches an event which in turn may update the view. - this.endChange_(); - }, - - /** - * Whether a given item is selected or not. - * @param {*} item The item to check. - * @return {boolean} Whether an item is selected. - */ - getItemSelected: function(item) { - var uid = this.list.itemToUid(item); - return uid in this.selectedItems_; - }, - - /** - * This is used to begin batching changes. Call {@code endChange_} when you - * are done making changes. - * @private - */ - beginChange_: function() { - if (!this.changeCount_) { - this.changeCount_ = 0; - this.changedUids_ = {}; - } - this.changeCount_++; - }, - - /** - * Call this after changes are done and it will dispatch a change event if - * any changes were actually done. - * @private - */ - endChange_: function() { - this.changeCount_--; - if (!this.changeCount_) { - var uids = Object.keys(this.changedUids_); - if (uids.length) { - var e = new Event('change'); - e.changes = uids.map(function(uid) { - return { - uid: uid, - selected: this.changedUids_[uid] - }; - }, this); - this.dispatchEvent(e); - } - delete this.changedUids_; - delete this.changeCount_; - } - }, - - /** - * Called when an item is removed from the lisst. - * @param {cr.ui.ListItem} item The list item that was removed. - */ - remove: function(item) { - if (item == this.leadItem) - this.leadItem = this.getNextItem(item) || this.getPreviousItem(item); - if (item == this.anchorItem) - this.anchorItem = this.getNextItem(item) || this.getPreviousItem(item); - - // Deselect when removing items. - if (this.getItemSelected(item)) - this.setItemSelected(item, false); - }, - - /** - * Called when an item was added to the list. - * @param {cr.ui.ListItem} item The list item to add. - */ - add: function(item) { - // We could (should?) check if the item is selected here and update the - // selection model. - } - }; - - /** - * The anchorItem is used with multiple selection. - * @type {*} - */ - cr.defineProperty(ListSelectionModel, 'anchorItem', cr.PropertyKind.JS, null); - - /** - * The leadItem is used with multiple selection and it is the item that the - * user is moving uysing the arrow keys. - * @type {*} - */ - cr.defineProperty(ListSelectionModel, 'leadItem', cr.PropertyKind.JS, null); - - return { - ListSelectionModel: ListSelectionModel - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/menu.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/menu.js deleted file mode 100644 index 1145d0f..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/menu.js +++ /dev/null @@ -1,157 +0,0 @@ -// 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. - -cr.define('cr.ui', function() { - - const MenuItem = cr.ui.MenuItem; - - /** - * Creates a new menu element. - * @param {Object=} opt_propertyBag Optional properties. - * @constructor - * @extends {HTMLMenuElement} - */ - var Menu = cr.ui.define('menu'); - - Menu.prototype = { - __proto__: HTMLMenuElement.prototype, - - /** - * Initializes the menu element. - */ - decorate: function() { - this.addEventListener('mouseover', this.handleMouseOver_); - this.addEventListener('mouseout', this.handleMouseOut_); - - // Decorate the children as menu items. - var children = this.children; - for (var i = 0, child; child = children[i]; i++) { - cr.ui.decorate(child, MenuItem); - } - }, - - /** - * Walks up the ancestors until a menu item belonging to this menu is found. - * @param {Element} el - * @return {cr.ui.MenuItem} The found menu item or null. - * @private - */ - findMenuItem_: function(el) { - while (el && el.parentNode != this) { - el = el.parentNode; - } - return el; - }, - - /** - * Handles mouseover events and selects the hovered item. - * @param {Event} e The mouseover event. - * @private - */ - handleMouseOver_: function(e) { - var overItem = this.findMenuItem_(e.target); - this.selectedItem = overItem; - }, - - /** - * Handles mouseout events and deselects any selected item. - * @param {Event} e The mouseout event. - * @private - */ - handleMouseOut_: function(e) { - this.selectedItem = null; - }, - - /** - * The index of the selected item. - * @type {boolean} - */ - // getter and default value is defined using cr.defineProperty. - set selectedIndex(selectedIndex) { - if (this.selectedIndex_ != selectedIndex) { - var oldSelectedItem = this.selectedItem; - this.selectedIndex_ = selectedIndex; - if (oldSelectedItem) - oldSelectedItem.selected = false; - var item = this.selectedItem; - if (item) - item.selected = true; - - cr.dispatchSimpleEvent(this, 'change'); - } - }, - - /** - * The selected menu item or null if none. - * @type {cr.ui.MenuItem} - */ - get selectedItem() { - return this.children[this.selectedIndex]; - }, - set selectedItem(item) { - var index = Array.prototype.indexOf.call(this.children, item); - this.selectedIndex = index; - }, - - /** - * This is the function that handles keyboard navigation. This is usually - * called by the element responsible for managing the menu. - * @param {Event} e The keydown event object. - * @return {boolean} Whether the event was handled be the menu. - */ - handleKeyDown: function(e) { - var item = this.selectedItem; - - var self = this; - function selectNextVisible(m) { - var children = self.children; - var len = children.length; - var i = self.selectedIndex; - if (i == -1 && m == -1) { - // Edge case when we need to go the last item fisrt. - i = 0; - } - while (true) { - i = (i + m + len) % len; - item = children[i]; - if (item && !item.isSeparator() && !item.hidden) - break; - } - if (item) - self.selectedIndex = i; - } - - switch (e.keyIdentifier) { - case 'Down': - selectNextVisible(1); - return true; - case 'Up': - selectNextVisible(-1); - return true; - case 'Enter': - case 'U+0020': // Space - if (item) { - if (cr.dispatchSimpleEvent(item, 'activate', true, true)) { - if (item.command) - item.command.execute(); - } - } - return true; - } - - return false; - } - }; - - /** - * The selected menu item. - * @type {number} - */ - cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS, -1); - - // Export - return { - Menu: Menu - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/menubutton.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/menubutton.js deleted file mode 100644 index cd8defb..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/menubutton.js +++ /dev/null @@ -1,167 +0,0 @@ -// 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. - -cr.define('cr.ui', function() { - const Menu = cr.ui.Menu; - - /** - * Creates a new menu button element. - * @param {Object=} opt_propertyBag Optional properties. - * @constructor - * @extends {HTMLButtonElement} - */ - var MenuButton = cr.ui.define('button'); - - MenuButton.prototype = { - __proto__: HTMLButtonElement.prototype, - - /** - * Initializes the menu button. - */ - decorate: function() { - this.addEventListener('mousedown', this); - this.addEventListener('keydown', this); - - var menu; - if ((menu = this.getAttribute('menu'))) - this.menu = menu; - }, - - /** - * The menu associated with the menu button. - * @type {cr.ui.Menu} - */ - get menu() { - return this.menu_; - }, - set menu(menu) { - if (typeof menu == 'string' && menu[0] == '#') { - menu = this.ownerDocument.getElementById(menu.slice(1)); - cr.ui.decorate(menu, Menu); - } - - this.menu_ = menu; - if (menu) { - if (menu.id) - this.setAttribute('menu', '#' + menu.id); - } - }, - - /** - * Handles event callbacks. - * @param {Event} e The event object. - */ - handleEvent: function(e) { - if (!this.menu) - return; - - switch (e.type) { - case 'mousedown': - if (e.currentTarget == this.ownerDocument) { - if (!this.contains(e.target) && !this.menu.contains(e.target)) - this.hideMenu(); - else - e.preventDefault(); - } else { - if (this.isMenuShown()) { - this.hideMenu(); - } else if (e.button == 0) { // Only show the menu when using left - // mouse button. - this.showMenu(); - // Prevent the button from stealing focus on mousedown. - e.preventDefault(); - } - } - break; - case 'keydown': - this.handleKeyDown(e); - // If the menu is visible we let it handle all the keyboard events. - if (this.isMenuShown() && e.currentTarget == this.ownerDocument) { - this.menu.handleKeyDown(e); - e.preventDefault(); - e.stopPropagation(); - } - break; - - case 'activate': - case 'blur': - this.hideMenu(); - break; - } - }, - - /** - * Shows the menu. - */ - showMenu: function() { - this.menu.style.display = 'block'; - // when the menu is shown we steal all keyboard events. - this.ownerDocument.addEventListener('keydown', this, true); - this.ownerDocument.addEventListener('mousedown', this, true); - this.ownerDocument.addEventListener('blur', this, true); - this.menu.addEventListener('activate', this); - this.positionMenu_(); - }, - - /** - * Hides the menu. - */ - hideMenu: function() { - this.menu.style.display = 'none'; - this.ownerDocument.removeEventListener('keydown', this, true); - this.ownerDocument.removeEventListener('mousedown', this, true); - this.ownerDocument.removeEventListener('blur', this, true); - this.menu.removeEventListener('activate', this); - this.menu.selectedIndex = -1; - }, - - /** - * Whether the menu is shown. - */ - isMenuShown: function() { - return window.getComputedStyle(this.menu).display != 'none'; - }, - - /** - * Positions the menu below the menu button. At this point we do not use any - * advanced positioning logic to ensure the menu fits in the viewport. - * @private - */ - positionMenu_: function() { - var buttonRect = this.getBoundingClientRect(); - this.menu.style.top = buttonRect.bottom + 'px'; - if (getComputedStyle(this).direction == 'rtl') { - var menuRect = this.menu.getBoundingClientRect(); - this.menu.style.left = buttonRect.right - menuRect.width + 'px'; - } else { - this.menu.style.left = buttonRect.left + 'px'; - } - }, - - /** - * Handles the keydown event for the menu button. - */ - handleKeyDown: function(e) { - switch (e.keyIdentifier) { - case 'Down': - case 'Up': - case 'Enter': - case 'U+0020': // Space - if (!this.isMenuShown()) - this.showMenu(); - e.preventDefault(); - break; - case 'Esc': - case 'U+001B': // Maybe this is remote desktop playing a prank? - this.hideMenu(); - break; - } - } - }; - - // Export - return { - MenuButton: MenuButton - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/menuitem.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/menuitem.js deleted file mode 100644 index 5c66f17..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/menuitem.js +++ /dev/null @@ -1,147 +0,0 @@ -// 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. - -cr.define('cr.ui', function() { - const Command = cr.ui.Command; - - /** - * Creates a new menu item element. - * @param {Object=} opt_propertyBag Optional properties. - * @constructor - * @extends {HTMLButtonElement} - */ - var MenuItem = cr.ui.define('button'); - - /** - * Creates a new menu separator element. - * @return {cr.ui.MenuItem} - */ - MenuItem.createSeparator = function() { - var el = cr.doc.createElement('hr'); - MenuItem.decorate(el); - return el; - }; - - MenuItem.prototype = { - __proto__: HTMLButtonElement.prototype, - - /** - * Initializes the menu item. - */ - decorate: function() { - var commandId; - if ((commandId = this.getAttribute('command'))) - this.command = commandId; - - this.addEventListener('mouseup', this.handleMouseUp_); - }, - - /** - * The command associated with this menu item. If this is set to a string - * of the form "#element-id" then the element is looked up in the document - * of the command. - * @type {cr.ui.Command} - */ - command_: null, - get command() { - return this.command_; - }, - set command(command) { - if (this.command_) { - this.command_.removeEventListener('labelChange', this); - this.command_.removeEventListener('disabledChange', this); - this.command_.removeEventListener('hiddenChange', this); - } - - if (typeof command == 'string' && command[0] == '#') { - command = this.ownerDocument.getElementById(command.slice(1)); - cr.ui.decorate(command, Command); - } - - this.command_ = command; - if (command) { - if (command.id) - this.setAttribute('command', '#' + command.id); - - this.label = command.label; - this.disabled = command.disabled; - this.hidden = command.hidden; - - this.command_.addEventListener('labelChange', this); - this.command_.addEventListener('disabledChange', this); - this.command_.addEventListener('hiddenChange', this); - } - }, - - /** - * The text label. - * @type {string} - */ - get label() { - return this.textContent; - }, - set label(label) { - this.textContent = label; - }, - - /** - * @return {boolean} Whether the menu item is a separator. - */ - isSeparator: function() { - return this.tagName == 'HR'; - }, - - /** - * Handles mouseup events. This dispatches an active event and if there - * is an assiciated command then that is executed. - * @param {Event} The mouseup event object. - * @private - */ - handleMouseUp_: function(e) { - if (!this.disabled && !this.isSeparator()) { - // Dispatch command event followed by executing the command object. - if (cr.dispatchSimpleEvent(this, 'activate', true, true)) { - var command = this.command; - if (command) - command.execute(); - } - } - }, - - /** - * Handles changes to the associated command. - * @param {Event} e The event object. - */ - handleEvent: function(e) { - switch (e.type) { - case 'disabledChange': - this.disabled = this.command.disabled; - break; - case 'hiddenChange': - this.hidden = this.command.hidden; - break; - case 'labelChange': - this.label = this.command.label; - break; - } - } - }; - - /** - * Whether the menu item is hidden or not. - * @type {boolean} - */ - cr.defineProperty(MenuItem, 'hidden', cr.PropertyKind.BOOL_ATTR); - - /** - * Whether the menu item is selected or not. - * @type {boolean} - */ - cr.defineProperty(MenuItem, 'selected', cr.PropertyKind.BOOL_ATTR); - - // Export - return { - MenuItem: MenuItem - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/splitter.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/splitter.js deleted file mode 100644 index 4f9510e..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/splitter.js +++ /dev/null @@ -1,154 +0,0 @@ -// 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 implements a splitter element which can be used to resize - * elements in split panes. - * - * The parent of the splitter should be an hbox (display: -webkit-box) with at - * least one previous element sibling. The splitter controls the width of the - * element before it. - * - *
- *
...
- *
- * ... - *
- * - */ - -cr.define('cr.ui', function() { - // TODO(arv): Currently this only supports horizontal layout. - // TODO(arv): This ignores min-width and max-width of the elements to the - // right of the splitter. - - /** - * Returns the computed style width of an element. - * @param {!Element} el The element to get the width of. - * @return {number} The width in pixels. - */ - function getComputedWidth(el) { - return parseFloat(el.ownerDocument.defaultView.getComputedStyle(el).width) / - getZoomFactor(el.ownerDocument); - } - - /** - * This uses a WebKit bug to work around the same bug. getComputedStyle does - * not take the page zoom into account so it returns the physical pixels - * instead of the logical pixel size. - * @param {!Document} doc The document to get the page zoom factor for. - * @param {number} The zoom factor of the document. - */ - function getZoomFactor(doc) { - var dummyElement = doc.createElement('div'); - dummyElement.style.cssText = - 'position:absolute;width:100px;height:100px;top:-1000px;overflow:hidden'; - doc.body.appendChild(dummyElement); - var cs = doc.defaultView.getComputedStyle(dummyElement); - var rect = dummyElement.getBoundingClientRect(); - var zoomFactor = parseFloat(cs.width) / 100; - doc.body.removeChild(dummyElement); - return zoomFactor; - } - - /** - * Creates a new splitter element. - * @param {Object=} opt_propertyBag Optional properties. - * @constructor - * @extends {HTMLDivElement} - */ - var Splitter = cr.ui.define('div'); - - Splitter.prototype = { - __proto__: HTMLDivElement.prototype, - - /** - * Initializes the element. - */ - decorate: function() { - this.addEventListener('mousedown', cr.bind(this.handleMouseDown_, this), - true); - }, - - /** - * Starts the dragging of the splitter. - * @param {!Event} e The mouse event that started the drag. - */ - startDrag: function(e) { - if (!this.boundHandleMouseMove_) { - this.boundHandleMouseMove_ = cr.bind(this.handleMouseMove_, this); - this.boundHandleMouseUp_ = cr.bind(this.handleMouseUp_, this); - } - - var doc = this.ownerDocument; - - // Use capturing events on the document to get events when the mouse - // leaves the document. - doc.addEventListener('mousemove',this.boundHandleMouseMove_, true); - doc.addEventListener('mouseup', this.boundHandleMouseUp_, true); - - // Use the computed width style as the base so that we can ignore what - // box sizing the element has. - var leftComponent = this.previousElementSibling; - var computedWidth = getComputedWidth(leftComponent); - this.startX_ = e.clientX; - this.startWidth_ = computedWidth - }, - - /** - * Ends the dragging of the splitter. This fires a "resize" event if the - * size changed. - */ - endDrag: function() { - var doc = this.ownerDocument; - doc.removeEventListener('mousemove', this.boundHandleMouseMove_, true); - doc.removeEventListener('mouseup', this.boundHandleMouseUp_, true); - - // Check if the size changed. - var leftComponent = this.previousElementSibling; - var computedWidth = getComputedWidth(leftComponent); - if (this.startWidth_ != computedWidth) - cr.dispatchSimpleEvent(this, 'resize'); - }, - - /** - * Handles the mousedown event which starts the dragging of the splitter. - * @param {!Event} e The mouse event. - * @private - */ - handleMouseDown_: function(e) { - this.startDrag(e); - // Default action is to start selection and to move focus. - e.preventDefault(); - }, - - /** - * Handles the mousemove event which moves the splitter as the user moves - * the mouse. - * @param {!Event} e The mouse event. - * @private - */ - handleMouseMove_: function(e) { - var leftComponent = this.previousElementSibling; - var rtl = this.ownerDocument.defaultView.getComputedStyle(this). - direction == 'rtl'; - var dirMultiplier = rtl ? -1 : 1; - leftComponent.style.width = this.startWidth_ + - dirMultiplier * (e.clientX - this.startX_) + 'px'; - }, - - /** - * Handles the mouse up event which ends the dragging of the splitter. - * @param {!Event} e The mouse event. - * @private - */ - handleMouseUp_: function(e) { - this.endDrag(); - } - }; - - return { - Splitter: Splitter - } -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr/ui/tree.js b/chrome/browser/resources/bookmark_manager/js/cr/ui/tree.js deleted file mode 100644 index 9e18321..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr/ui/tree.js +++ /dev/null @@ -1,664 +0,0 @@ -// 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. - -cr.define('cr.ui', function() { - // require cr.ui.define - // require cr.ui.limitInputWidth - - /** - * The number of pixels to indent per level. - * @type {number} - */ - const INDENT = 20; - - /** - * Returns the computed style for an element. - * @param {!Element} el The element to get the computed style for. - * @return {!CSSStyleDeclaration} The computed style. - */ - function getComputedStyle(el) { - return el.ownerDocument.defaultView.getComputedStyle(el); - } - - /** - * Helper function that finds the first ancestor tree item. - * @param {!Element} el The element to start searching from. - * @return {cr.ui.TreeItem} The found tree item or null if not found. - */ - function findTreeItem(el) { - while (el && !(el instanceof TreeItem)) { - el = el.parentNode; - } - return el; - } - - /** - * Creates a new tree element. - * @param {Object=} opt_propertyBag Optional properties. - * @constructor - * @extends {HTMLElement} - */ - var Tree = cr.ui.define('tree'); - - Tree.prototype = { - __proto__: HTMLElement.prototype, - - /** - * Initializes the element. - */ - decorate: function() { - // Make list focusable - if (!this.hasAttribute('tabindex')) - this.tabIndex = 0; - - this.addEventListener('click', this.handleClick); - this.addEventListener('mousedown', this.handleMouseDown); - this.addEventListener('dblclick', this.handleDblClick); - this.addEventListener('keydown', this.handleKeyDown); - }, - - /** - * Returns the tree item that are children of this tree. - */ - get items() { - return this.children; - }, - - /** - * Adds a tree item to the tree. - * @param {!cr.ui.TreeItem} treeItem The item to add. - */ - add: function(treeItem) { - this.addAt(treeItem, 0xffffffff); - }, - - /** - * Adds a tree item at the given index. - * @param {!cr.ui.TreeItem} treeItem The item to add. - * @param {number} index The index where we want to add the item. - */ - addAt: function(treeItem, index) { - this.insertBefore(treeItem, this.children[index]); - treeItem.setDepth_(this.depth + 1); - }, - - /** - * Removes a tree item child. - * @param {!cr.ui.TreeItem} treeItem The tree item to remove. - */ - remove: function(treeItem) { - this.removeChild(treeItem); - }, - - /** - * The depth of the node. This is 0 for the tree itself. - * @type {number} - */ - get depth() { - return 0; - }, - - /** - * Handles click events on the tree and forwards the event to the relevant - * tree items as necesary. - * @param {Event} e The click event object. - */ - handleClick: function(e) { - var treeItem = findTreeItem(e.target); - if (treeItem) - treeItem.handleClick(e); - }, - - handleMouseDown: function(e) { - if (e.button == 2) // right - this.handleClick(e); - }, - - /** - * Handles double click events on the tree. - * @param {Event} e The dblclick event object. - */ - handleDblClick: function(e) { - var treeItem = findTreeItem(e.target); - if (treeItem) - treeItem.expanded = !treeItem.expanded; - }, - - /** - * Handles keydown events on the tree and updates selection and exanding - * of tree items. - * @param {Event} e The click event object. - */ - handleKeyDown: function(e) { - var itemToSelect; - if (e.ctrlKey) - return; - - var item = this.selectedItem; - - var rtl = getComputedStyle(item).direction == 'rtl'; - - switch (e.keyIdentifier) { - case 'Up': - itemToSelect = item ? getPrevious(item) : - this.items[this.items.length - 1]; - break; - case 'Down': - itemToSelect = item ? getNext(item) : - this.items[0]; - break; - case 'Left': - case 'Right': - // Don't let back/forward keyboard shortcuts be used. - if (!cr.isMac && e.altKey || cr.isMac && e.metaKey) - break; - - if (e.keyIdentifier == 'Left' && !rtl || - e.keyIdentifier == 'Right' && rtl) { - if (item.expanded) - item.expanded = false; - else - itemToSelect = findTreeItem(item.parentNode); - } else { - if (!item.expanded) - item.expanded = true; - else - itemToSelect = item.items[0]; - } - break; - case 'Home': - itemToSelect = this.items[0]; - break; - case 'End': - itemToSelect = this.items[this.items.length - 1]; - break; - } - - if (itemToSelect) { - itemToSelect.selected = true; - e.preventDefault(); - } - }, - - /** - * The selected tree item or null if none. - * @type {cr.ui.TreeItem} - */ - get selectedItem() { - return this.selectedItem_ || null; - }, - set selectedItem(item) { - var oldSelectedItem = this.selectedItem_; - if (oldSelectedItem != item) { - // Set the selectedItem_ before deselecting the old item since we only - // want one change when moving between items. - this.selectedItem_ = item; - - if (oldSelectedItem) - oldSelectedItem.selected = false; - - if (item) - item.selected = true; - - cr.dispatchSimpleEvent(this, 'change'); - } - }, - - /** - * @return {!ClientRect} The rect to use for the context menu. - */ - getRectForContextMenu: function() { - // TODO(arv): Add trait support so we can share more code between trees - // and lists. - if (this.selectedItem) - return this.selectedItem.rowElement.getBoundingClientRect(); - return this.getBoundingClientRect(); - } - }; - - /** - * This is used as a blueprint for new tree item elements. - * @type {!HTMLElement} - */ - var treeItemProto = (function() { - var treeItem = cr.doc.createElement('div'); - treeItem.className = 'tree-item'; - treeItem.innerHTML = '
' + - '' + - '' + - '
' + - '
'; - return treeItem; - })(); - - /** - * Creates a new tree item. - * @param {Object=} opt_propertyBag Optional properties. - * @constructor - * @extends {HTMLElement} - */ - var TreeItem = cr.ui.define(function() { - return treeItemProto.cloneNode(true); - }); - - TreeItem.prototype = { - __proto__: HTMLElement.prototype, - - /** - * Initializes the element. - */ - decorate: function() { - - }, - - /** - * The tree items children. - */ - get items() { - return this.lastElementChild.children; - }, - - /** - * The depth of the tree item. - * @type {number} - */ - depth_: 0, - get depth() { - return this.depth_; - }, - - /** - * Sets the depth. - * @param {number} depth The new depth. - * @private - */ - setDepth_: function(depth) { - if (depth != this.depth_) { - this.rowElement.style.WebkitPaddingStart = Math.max(0, depth - 1) * - INDENT + 'px'; - this.depth_ = depth; - var items = this.items; - for (var i = 0, item; item = items[i]; i++) { - item.setDepth_(depth + 1); - } - } - }, - - /** - * Adds a tree item as a child. - * @param {!cr.ui.TreeItem} child The child to add. - */ - add: function(child) { - this.addAt(child, 0xffffffff); - }, - - /** - * Adds a tree item as a child at a given index. - * @param {!cr.ui.TreeItem} child The child to add. - * @param {number} index The index where to add the child. - */ - addAt: function(child, index) { - this.lastElementChild.insertBefore(child, this.items[index]); - if (this.items.length == 1) - this.hasChildren_ = true; - child.setDepth_(this.depth + 1); - }, - - /** - * Removes a child. - * @param {!cr.ui.TreeItem} child The tree item child to remove. - */ - remove: function(child) { - // If we removed the selected item we should become selected. - var tree = this.tree; - var selectedItem = tree.selectedItem; - if (selectedItem && child.contains(selectedItem)) - this.selected = true; - - this.lastElementChild.removeChild(child); - if (this.items.length == 0) - this.hasChildren_ = false; - }, - - /** - * The parent tree item. - * @type {!cr.ui.Tree|cr.ui.TreeItem} - */ - get parentItem() { - var p = this.parentNode; - while (p && !(p instanceof TreeItem) && !(p instanceof Tree)) { - p = p.parentNode; - } - return p; - }, - - /** - * The tree that the tree item belongs to or null of no added to a tree. - * @type {cr.ui.Tree} - */ - get tree() { - var t = this.parentItem; - while (t && !(t instanceof Tree)) { - t = t.parentItem; - } - return t; - }, - - /** - * Whether the tree item is expanded or not. - * @type {boolean} - */ - get expanded() { - return this.hasAttribute('expanded'); - }, - set expanded(b) { - if (this.expanded == b) - return; - - var treeChildren = this.lastElementChild; - - if (b) { - if (this.mayHaveChildren_) { - this.setAttribute('expanded', ''); - treeChildren.setAttribute('expanded', ''); - cr.dispatchSimpleEvent(this, 'expand', true); - this.scrollIntoViewIfNeeded(false); - } - } else { - var tree = this.tree; - if (tree && !this.selected) { - var oldSelected = tree.selectedItem; - if (oldSelected && this.contains(oldSelected)) - this.selected = true; - } - this.removeAttribute('expanded'); - treeChildren.removeAttribute('expanded'); - cr.dispatchSimpleEvent(this, 'collapse', true); - } - }, - - /** - * Expands all parent items. - */ - reveal: function() { - var pi = this.parentItem; - while (pi && !(pi instanceof Tree)) { - pi.expanded = true; - pi = pi.parentItem; - } - }, - - /** - * The element representing the row that gets highlighted. - * @type {!HTMLElement} - */ - get rowElement() { - return this.firstElementChild; - }, - - /** - * The element containing the label text and the icon. - * @type {!HTMLElement} - */ - get labelElement() { - return this.firstElementChild.lastElementChild; - }, - - /** - * The label text. - * @type {string} - */ - get label() { - return this.labelElement.textContent; - }, - set label(s) { - this.labelElement.textContent = s; - }, - - /** - * The URL for the icon. - * @type {string} - */ - get icon() { - return getComputedStyle(this.labelElement).backgroundImage.slice(4, -1); - }, - set icon(icon) { - return this.labelElement.style.backgroundImage = url(icon); - }, - - /** - * Whether the tree item is selected or not. - * @type {boolean} - */ - get selected() { - return this.hasAttribute('selected'); - }, - set selected(b) { - if (this.selected == b) - return; - var rowItem = this.firstElementChild; - var tree = this.tree; - if (b) { - this.setAttribute('selected', ''); - rowItem.setAttribute('selected', ''); - this.labelElement.scrollIntoViewIfNeeded(false); - if (tree) - tree.selectedItem = this; - } else { - this.removeAttribute('selected'); - rowItem.removeAttribute('selected'); - if (tree && tree.selectedItem == this) - tree.selectedItem = null; - } - }, - - /** - * Whether the tree item has children. - * @type {boolean} - */ - get mayHaveChildren_() { - return this.hasAttribute('may-have-children'); - }, - set mayHaveChildren_(b) { - var rowItem = this.firstElementChild; - if (b) { - this.setAttribute('may-have-children', ''); - rowItem.setAttribute('may-have-children', ''); - } else { - this.removeAttribute('may-have-children'); - rowItem.removeAttribute('may-have-children'); - } - }, - - /** - * Whether the tree item has children. - * @type {boolean} - */ - get hasChildren() { - return !!this.items[0]; - }, - - /** - * Whether the tree item has children. - * @type {boolean} - * @private - */ - set hasChildren_(b) { - var rowItem = this.firstElementChild; - this.setAttribute('has-children', b); - rowItem.setAttribute('has-children', b); - if (b) - this.mayHaveChildren_ = true; - }, - - /** - * Called when the user clicks on a tree item. This is forwarded from the - * cr.ui.Tree. - * @param {Event} e The click event. - */ - handleClick: function(e) { - if (e.target.className == 'expand-icon') - this.expanded = !this.expanded; - else - this.selected = true; - }, - - /** - * Makes the tree item user editable. If the user renamed the item a - * bubbling {@code rename} event is fired. - * @type {boolean} - */ - set editing(editing) { - var oldEditing = this.editing; - if (editing == oldEditing) - return; - - var self = this; - var labelEl = this.labelElement; - var text = this.label; - var input; - - // Handles enter and escape which trigger reset and commit respectively. - function handleKeydown(e) { - // Make sure that the tree does not handle the key. - e.stopPropagation(); - - // Calling tree.focus blurs the input which will make the tree item - // non editable. - switch (e.keyIdentifier) { - case 'U+001B': // Esc - input.value = text; - // fall through - case 'Enter': - self.tree.focus(); - } - } - - function stopPropagation(e) { - e.stopPropagation(); - } - - if (editing) { - this.selected = true; - this.setAttribute('editing', ''); - this.draggable = false; - - // We create an input[type=text] and copy over the label value. When - // the input loses focus we set editing to false again. - input = this.ownerDocument.createElement('input'); - input.value = text; - if (labelEl.firstChild) - labelEl.replaceChild(input, labelEl.firstChild); - else - labelEl.appendChild(input); - - input.addEventListener('keydown', handleKeydown); - input.addEventListener('blur', cr.bind(function() { - this.editing = false; - }, this)); - - // Make sure that double clicks do not expand and collapse the tree - // item. - var eventsToStop = ['mousedown', 'mouseup', 'contextmenu', 'dblclick']; - eventsToStop.forEach(function(type) { - input.addEventListener(type, stopPropagation); - }); - - // Wait for the input element to recieve focus before sizing it. - var rowElement = this.rowElement; - function onFocus() { - input.removeEventListener('focus', onFocus); - // 20 = the padding and border of the tree-row - cr.ui.limitInputWidth(input, rowElement, 20); - } - input.addEventListener('focus', onFocus); - input.focus(); - input.select(); - - this.oldLabel_ = text; - } else { - this.removeAttribute('editing'); - this.draggable = true; - input = labelEl.firstChild; - var value = input.value; - if (/^\s*$/.test(value)) { - labelEl.textContent = this.oldLabel_; - } else { - labelEl.textContent = value; - if (value != this.oldLabel_) { - cr.dispatchSimpleEvent(this, 'rename', true); - } - } - delete this.oldLabel_; - } - }, - - get editing() { - return this.hasAttribute('editing'); - } - }; - - /** - * Helper function that returns the next visible tree item. - * @param {cr.ui.TreeItem} item The tree item. - * @retrun {cr.ui.TreeItem} The found item or null. - */ - function getNext(item) { - if (item.expanded) { - var firstChild = item.items[0]; - if (firstChild) { - return firstChild; - } - } - - return getNextHelper(item); - } - - /** - * Another helper function that returns the next visible tree item. - * @param {cr.ui.TreeItem} item The tree item. - * @retrun {cr.ui.TreeItem} The found item or null. - */ - function getNextHelper(item) { - if (!item) - return null; - - var nextSibling = item.nextElementSibling; - if (nextSibling) { - return nextSibling; - } - return getNextHelper(item.parentItem); - } - - /** - * Helper function that returns the previous visible tree item. - * @param {cr.ui.TreeItem} item The tree item. - * @retrun {cr.ui.TreeItem} The found item or null. - */ - function getPrevious(item) { - var previousSibling = item.previousElementSibling; - return previousSibling ? getLastHelper(previousSibling) : item.parentItem; - } - - /** - * Helper function that returns the last visible tree item in the subtree. - * @param {cr.ui.TreeItem} item The item to find the last visible item for. - * @return {cr.ui.TreeItem} The found item or null. - */ - function getLastHelper(item) { - if (!item) - return null; - if (item.expanded && item.hasChildren) { - var lastChild = item.items[item.items.length - 1]; - return getLastHelper(lastChild); - } - return item; - } - - // Export - return { - Tree: Tree, - TreeItem: TreeItem - }; -}); diff --git a/chrome/browser/resources/bookmark_manager/js/cr_test.html b/chrome/browser/resources/bookmark_manager/js/cr_test.html deleted file mode 100644 index 5d6c16e..0000000 --- a/chrome/browser/resources/bookmark_manager/js/cr_test.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/chrome/browser/resources/bookmark_manager/js/i18ntemplate.js b/chrome/browser/resources/bookmark_manager/js/i18ntemplate.js deleted file mode 100644 index 8166ddc..0000000 --- a/chrome/browser/resources/bookmark_manager/js/i18ntemplate.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * @fileoverview This is a simple template engine inspired by JsTemplates - * optimized for i18n. - * - * It currently supports two handlers: - * - * * i18n-content which sets the textContent of the element - * - * - * i18nTemplate.process(element, {'myContent': 'Content'}); - * - * * i18n-values is a list of attribute-value or property-value pairs. - * Properties are prefixed with a '.' and can contain nested properties. - * - * - * i18nTemplate.process(element, { - * 'myTitle': 'Title', - * 'fontSize': '13px' - * }); - */ - -var i18nTemplate = (function() { - /** - * This provides the handlers for the templating engine. The key is used as - * the attribute name and the value is the function that gets called for every - * single node that has this attribute. - * @type {Object} - */ - var handlers = { - /** - * This handler sets the textContent of the element. - */ - 'i18n-content': function(element, attributeValue, obj) { - element.textContent = obj[attributeValue]; - }, - - /** - * This is used to set HTML attributes and DOM properties,. The syntax is: - * attributename:key; - * .domProperty:key; - * .nested.dom.property:key - */ - 'i18n-values': function(element, attributeValue, obj) { - var parts = attributeValue.replace(/\s/g, '').split(/;/); - for (var j = 0; j < parts.length; j++) { - var a = parts[j].match(/^([^:]+):(.+)$/); - if (a) { - var propName = a[1]; - var propExpr = a[2]; - - // Ignore missing properties - if (propExpr in obj) { - var value = obj[propExpr]; - if (propName.charAt(0) == '.') { - var path = propName.slice(1).split('.'); - var object = element; - while (object && path.length > 1) { - object = object[path.shift()]; - } - if (object) { - object[path] = value; - // In case we set innerHTML (ignoring others) we need to - // recursively check the content - if (path == 'innerHTML') { - process(element, obj); - } - } - } else { - element.setAttribute(propName, value); - } - } else { - console.warn('i18n-values: Missing value for "' + propExpr + '"'); - } - } - } - } - }; - - var attributeNames = []; - for (var key in handlers) { - attributeNames.push(key); - } - var selector = '[' + attributeNames.join('],[') + ']'; - - /** - * Processes a DOM tree with the {@code obj} map. - */ - function process(node, obj) { - var elements = node.querySelectorAll(selector); - for (var element, i = 0; element = elements[i]; i++) { - for (var j = 0; j < attributeNames.length; j++) { - var name = attributeNames[j]; - var att = element.getAttribute(name); - if (att != null) { - handlers[name](element, att, obj); - } - } - } - } - - return { - process: process - }; -})(); diff --git a/chrome/browser/resources/bookmark_manager/js/localstrings.js b/chrome/browser/resources/bookmark_manager/js/localstrings.js deleted file mode 100644 index 86f888b..0000000 --- a/chrome/browser/resources/bookmark_manager/js/localstrings.js +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2009-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. - -// TODO(arv): Namespace and share code with DOMUI - -/** - * The local strings get injected into the page usig a varaible named - * {@code templateData}. This class provides a simpler interface to access those - * strings. - * @constructor - */ -function LocalStrings() { -} - -LocalStrings.prototype = { - - /** - * The template data object. - * @type {Object} - */ - templateData: null, - - /** - * Gets a localized string by its id. - * @param {string} s The id of the string we want. - * @return {string} The localized string. - */ - getString: function(id) { - return this.templateData[id] || ''; - }, - - /** - * Returns a formatted localized string where all %s contents are replaced - * by the second argument and where $1 to $9 are replaced by the second to - * tenths arguments. - * @param {string} id The ID of the string we want. - * @param {string} v The string to include in the formatted string. - * @param {...string} The extra values to include in the fomatted output. - * @return {string} The formatted string. - */ - getStringF: function(id, v, var_args) { - // The localized messages should contain $n but they also use %s from time - // to time so we support both until all the messages have been unified. - var s = this.getString(id); - var args = arguments; - return s.replace(/%s|\$[$1-9]/g, function(m) { - if (m == '%s') - return v; - if (m == '$$') - return '$'; - return args[m[1]]; - }); - } -}; diff --git a/chrome/browser/resources/bookmark_manager/js/util.js b/chrome/browser/resources/bookmark_manager/js/util.js deleted file mode 100644 index 3fef5d2..0000000 --- a/chrome/browser/resources/bookmark_manager/js/util.js +++ /dev/null @@ -1,57 +0,0 @@ -// 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. - -/** - * The global object. - * @param {!Object} - */ -const global = this; - -/** - * Alias for document.getElementById. - * @param {string} id The ID of the element to find. - * @return {HTMLElement} The found element or null if not found. - */ -function $(id) { - return document.getElementById(id); -} - -/** - * Calls chrome.send with a callback and restores the original afterwards. - * @param {string} name The name of the message to send. - * @param {!Array} params The parameters to send. - * @param {string} callbackName The name of the function that the backend calls. - * @param {!Function} The function to call. - */ -function chromeSend(name, params, callbackName, callback) { - var old = global[callbackName]; - global[callbackName] = function() { - // restore - global[callbackName] = old; - - var args = Array.prototype.slice.call(arguments); - return callback.apply(global, args); - }; - chrome.send(name, params); -} - - -/** - * Generates a CSS url string. - * @param {string} s The URL to generate the CSS url for. - * @return {string} The CSS url string. - */ -function url(s) { - // http://www.w3.org/TR/css3-values/#uris - // Parentheses, commas, whitespace characters, single quotes (') and double - // quotes (") appearing in a URI must be escaped with a backslash - var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1'); - // WebKit has a bug when it comes to URLs that end with \ - // https://bugs.webkit.org/show_bug.cgi?id=28885 - if (/\\\\$/.test(s2)) { - // Add a space to work around the WebKit bug. - s2 += ' '; - } - return 'url("' + s2 + '")'; -} diff --git a/chrome/browser/resources/bookmark_manager/main.html b/chrome/browser/resources/bookmark_manager/main.html index 561a3ab..5b91326 100644 --- a/chrome/browser/resources/bookmark_manager/main.html +++ b/chrome/browser/resources/bookmark_manager/main.html @@ -10,34 +10,35 @@ found in the LICENSE file. - - - + + + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/chrome/browser/resources/bookmark_manager/manifest.json b/chrome/browser/resources/bookmark_manager/manifest.json index de24916..b9a1ea5 100644 --- a/chrome/browser/resources/bookmark_manager/manifest.json +++ b/chrome/browser/resources/bookmark_manager/manifest.json @@ -10,7 +10,8 @@ "bookmarks", "experimental", "tabs", - "chrome://favicon/" + "chrome://favicon/", + "chrome://resources/" ], "chrome_url_overrides": { "bookmarks": "main.html" diff --git a/chrome/browser/resources/shared/css/list.css b/chrome/browser/resources/shared/css/list.css new file mode 100644 index 0000000..5f91834 --- /dev/null +++ b/chrome/browser/resources/shared/css/list.css @@ -0,0 +1,62 @@ + +list { + overflow: auto; + outline: none; +} + +list > * { + -webkit-user-select: none; + background-color: rgba(255,255,255,0); + border: 1px solid rgba(255,255,255,0); /* transparent white */ + border-radius: 2px; + cursor: default; + line-height: 20px; + margin: -1px 0; + overflow: hidden; + padding: 0px 3px; + position: relative; /* to allow overlap */ + text-overflow: ellipsis; + white-space: pre; +} + +list > [lead] { + border-color: transparent; +} + +list:focus > [lead] { + border-color: hsl(214, 91%, 65%); + z-index: 2; +} + +list > [anchor] { + +} + +list > :hover { + border-color: hsl(214, 91%, 85%); + z-index: 1; + background-color: hsl(214, 91%, 97%); +} + +list > [selected] { + background-image: -webkit-gradient(linear, left top, left bottom, + from(rgba(255,255,255,0.8)), to(rgba(255,255,255,0))); +} + +list > [selected] { + border-color: hsl(0, 0%, 85%); + background-color: hsl(0,0%,90%); + z-index: 2; +} + +list:focus > [selected] { + background-color: hsl(214,91%,89%); + border-color: hsl(214, 91%, 65%); +} + +list:focus > [lead][selected], +list > [selected]:hover { + background-color: hsl(214, 91%, 87%); + border-color: hsl(214, 91%, 65%); +} + diff --git a/chrome/browser/resources/shared/css/menu.css b/chrome/browser/resources/shared/css/menu.css new file mode 100644 index 0000000..ef35992 --- /dev/null +++ b/chrome/browser/resources/shared/css/menu.css @@ -0,0 +1,48 @@ + +menu { + display: none; + position: absolute; + border: 1px solid #999; + -webkit-box-shadow: 2px 2px 3px hsla(0, 0%, 0%, .3); + color: black; + background-color: white; + left: 0; + white-space: nowrap; + z-index: 2; + padding: 2px; + margin: 0; + cursor: default; + border-radius: 4px; +} + +menu > * { + display: block; + margin: 0; + width: 100%; + text-align: start; +} + +menu > :not(hr) { + -webkit-appearance: none; + background: transparent; + font: inherit; + border: 0; + padding: 3px 8px; + overflow: hidden; + text-overflow: ellipsis; +} + +menu > hr { + border: 0; + border-top: 1px solid rgb(153, 153, 153); + margin: 2px 0; +} + +menu > [hidden] { + display: none; +} + +menu > [selected] { + background-color: hsl(213, 66%, 57%); + color: white; +} diff --git a/chrome/browser/resources/shared/css/tree.css b/chrome/browser/resources/shared/css/tree.css new file mode 100644 index 0000000..10bb708 --- /dev/null +++ b/chrome/browser/resources/shared/css/tree.css @@ -0,0 +1,153 @@ +tree { + outline: none; + overflow: auto; + display: block; +} + +.tree-item > .tree-row { + color: black; + -webkit-user-select: none; + border: 1px solid rgba(255,255,255,0); /* transparent white */ + background-color: rgba(255,255,255,0); + border-radius: 2px; + padding: 0px 3px; + line-height: 20px; + white-space: nowrap; + cursor: default; + position: relative; + margin: -1px 0; +} + +.expand-icon { + width: 16px; + height: 16px; + display: inline-block; + vertical-align: top; + position: relative; + top: 2px; + background-image: -webkit-canvas(tree-triangle); + background-position: 50% 50%; + background-repeat: no-repeat; + -webkit-transition: all .15s; + opacity: .6; + -webkit-transform: rotate(-90deg); +} + +html[dir=rtl] .expand-icon { + -webkit-transform: rotate(90deg); +} + +.tree-item[expanded] > .tree-row > .expand-icon { + background-image: -webkit-canvas(tree-triangle); + -webkit-transform: rotate(0deg); + opacity: .5; +} + +.tree-row .expand-icon { + visibility: hidden; +} + +.tree-row[may-have-children] .expand-icon { + visibility: visible; +} + +.tree-row[has-children=false] .expand-icon { + visibility: hidden; +} + +.tree-row:hover { + border-color: hsl(214, 91%, 85%); + z-index: 1; + background-color: hsl(214, 91%, 97%); +} + +/* + WebKit has a bug with attribute selectors so we apply selected to the tree row + as well. + + https://bugs.webkit.org/show_bug.cgi?id=12519 + +*/ +.tree-row[selected] { + background-image: -webkit-gradient(linear, left top, left bottom, + from(rgba(255,255,255,0.8)), to(rgba(255,255,255,0))); +} + +.tree-row[selected] { + border-color: hsl(0,0%,85%); + background-color: hsl(0, 0%, 90%); + z-index: 2; +} + +.tree-row[selected]:hover, +tree:focus .tree-row[selected] { + background-color: hsl(214, 91%, 89%); + border-color: #7da2ce; +} + +.tree-children[expanded] { + display: block; +} + +.tree-children { + display: none; +} + +.tree-item > .tree-row > * { + display: inline-block; + -webkit-box-sizing: border-box; +} + +.tree-label { + -webkit-padding-start: 20px; + background-image: url("../images/folder_closed.png"); + background-position: 0 50%; + background-repeat: no-repeat; + white-space: pre; +} + +/* We need to ensure that even empty labels take up space */ +.tree-label:empty:after { + content: " "; + white-space: pre; +} + +.tree-rename > .tree-row > .tree-label { + -webkit-user-select: auto; + -webkit-user-modify: read-write-plaintext-only; + background: white; + color: black; + outline: 1px solid black; +} + +html[dir=rtl] .tree-label { + background-position: 100% 50%; +} + +.tree-row[selected] > .tree-label { + background-image: url("../images/folder_open.png"); +} + +html[dir='rtl'] .tree-label { + background-image: url("../images/folder_closed_rtl.png"); +} + +html[dir='rtl'] .tree-row[selected] > .tree-label { + background-image: url("../images/folder_open_rtl.png"); +} + +.tree-item[editing] input { + /* Do not inherit the line-height */ + font-family: inherit; + font-size: inherit; + font-weight: inherit; + margin: -2px -8px -2px -3px; + padding: 1px 7px 1px 1px; + outline: none; +} + +html[dir=rtl] .tree-item[editing] input { + margin: -2px -3px -2px -8px; + padding: 1px 1px 1px 7px; +} + diff --git a/chrome/browser/resources/shared/css/tree.css.js b/chrome/browser/resources/shared/css/tree.css.js new file mode 100644 index 0000000..a6ce38e --- /dev/null +++ b/chrome/browser/resources/shared/css/tree.css.js @@ -0,0 +1,22 @@ +// 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. + +(function() { + var a = 7; + var a2 = a / 2; + var ctx = document.getCSSCanvasContext('2d', 'tree-triangle', a + 1, a2 + 2); + + ctx.fillStyle = '#000'; + ctx.translate(.5, .5); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, 1); + ctx.lineTo(a2, 1 + a2); + ctx.lineTo(a, 1); + ctx.lineTo(a, 0); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); +})(); diff --git a/chrome/browser/resources/shared/js/cr.js b/chrome/browser/resources/shared/js/cr.js new file mode 100644 index 0000000..9de94da --- /dev/null +++ b/chrome/browser/resources/shared/js/cr.js @@ -0,0 +1,314 @@ +// 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. + +const cr = (function() { + + /** + * Whether we are using a Mac or not. + * @type {boolean} + */ + const isMac = /Mac/.test(navigator.platform); + + /** + * Builds an object structure for the provided namespace path, + * ensuring that names that already exist are not overwritten. For + * example: + * "a.b.c" -> a = {};a.b={};a.b.c={}; + * @param {string} name Name of the object that this file defines. + * @param {*=} opt_object The object to expose at the end of the path. + * @param {Object=} opt_objectToExportTo The object to add the path to; + * default is {@code window}. + * @private + */ + function exportPath(name, opt_object, opt_objectToExportTo) { + var parts = name.split('.'); + var cur = opt_objectToExportTo || window /* global */; + + for (var part; parts.length && (part = parts.shift());) { + if (!parts.length && opt_object !== undefined) { + // last part and we have an object; use it + cur[part] = opt_object; + } else if (part in cur) { + cur = cur[part]; + } else { + cur = cur[part] = {}; + } + } + return cur; + }; + + /** + * Fires a property change event on the target. + * @param {EventTarget} target The target to dispatch the event on. + * @param {string} propertyName The name of the property that changed. + * @param {*} newValue The new value for the property. + * @param {*} oldValue The old value for the property. + */ + function dispatchPropertyChange(target, propertyName, newValue, oldValue) { + // TODO(arv): Depending on cr.Event here is a bit ugly. + var e = new cr.Event(propertyName + 'Change'); + e.propertyName = propertyName; + e.newValue = newValue; + e.oldValue = oldValue; + target.dispatchEvent(e); + } + + /** + * The kind of property to define in {@code defineProperty}. + * @enum {number} + */ + const PropertyKind = { + /** + * Plain old JS property where the backing data is stored as a "private" + * field on the object. + */ + JS: 'js', + + /** + * The property backing data is stored as an attribute on an element. + */ + ATTR: 'attr', + + /** + * The property backing data is stored as an attribute on an element. If the + * element has the attribute then the value is true. + */ + BOOL_ATTR: 'boolAttr' + }; + + /** + * Helper function for defineProperty that returns the getter to use for the + * property. + * @param {string} name + * @param {cr.PropertyKind} kind + * @param {*} defaultValue The default value. This is only used for the ATTR + * kind. + * @return {function():*} The getter for the property. + */ + function getGetter(name, kind, defaultValue) { + switch (kind) { + case PropertyKind.JS: + var privateName = name + '_'; + return function() { + return this[privateName]; + }; + case PropertyKind.ATTR: + // For attr with default value we return the default value if the + // element is missing the attribute. + if (defaultValue == undefined) { + return function() { + return this.getAttribute(name); + }; + } else { + return function() { + // WebKit uses null for non existant attributes. + var value = this.getAttribute(name); + return value !== null ? value : defaultValue; + }; + } + case PropertyKind.BOOL_ATTR: + // Boolean attributes don't support default values. + return function() { + return this.hasAttribute(name); + }; + } + } + + /** + * Helper function for defineProperty that returns the setter of the right + * kind. + * @param {string} name The name of the property we are defining the setter + * for. + * @param {cr.PropertyKind} kind The kind of property we are getting the + * setter for. + * @return {function(*):void} The function to use as a setter. + */ + function getSetter(name, kind) { + switch (kind) { + case PropertyKind.JS: + var privateName = name + '_'; + return function(value) { + var oldValue = this[privateName]; + if (value !== oldValue) { + this[privateName] = value; + dispatchPropertyChange(this, name, value, oldValue); + } + }; + + case PropertyKind.ATTR: + return function(value) { + var oldValue = this[name]; + if (value !== oldValue) { + this.setAttribute(name, value); + dispatchPropertyChange(this, name, value, oldValue); + } + }; + + case PropertyKind.BOOL_ATTR: + return function(value) { + var oldValue = this[name]; + if (value !== oldValue) { + if (value) + this.setAttribute(name, name); + else + this.removeAttribute(name); + dispatchPropertyChange(this, name, value, oldValue); + } + }; + } + } + + /** + * Defines a property on an object. When the setter changes the value a + * property change event with the type {@code name + 'Change'} is fired. + * @param {!Object} The object to define the property for. + * @param {string} The name of the property. + * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use. + * @param {*} opt_defaultValue The default value. + */ + function defineProperty(obj, name, opt_kind, opt_default) { + if (typeof obj == 'function') + obj = obj.prototype; + + var kind = opt_kind || PropertyKind.JS; + + if (!obj.__lookupGetter__(name)) { + // For js properties we set the default value on the prototype. + if (kind == PropertyKind.JS && arguments.length > 3) { + var privateName = name + '_'; + obj[privateName] = opt_default; + } + obj.__defineGetter__(name, getGetter(name, kind, opt_default)); + } + + if (!obj.__lookupSetter__(name)) { + obj.__defineSetter__(name, getSetter(name, kind)); + } + } + + /** + * Counter for use with createUid + */ + var uidCounter = 1; + + /** + * @return {number} A new unique ID. + */ + function createUid() { + return uidCounter++; + } + + /** + * Returns a unique ID for the item. This mutates the item so it needs to be + * an object + * @param {!Object} item The item to get the unique ID for. + * @return {number} The unique ID for the item. + */ + function getUid(item) { + if (item.hasOwnProperty('uid')) + return item.uid; + return item.uid = createUid(); + } + + /** + * Partially applies this function to a particular 'this object' and zero or + * more arguments. The result is a new function with some arguments of the + * first function pre-filled and the value of |this| 'pre-specified'. + * + * Remaining arguments specified at call-time are appended to the pre- + * specified ones. + * + * Usage: + *
var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
+   * barMethBound('arg3', 'arg4');
+ * + * @param {Function} fn A function to partially apply. + * @param {Object|undefined} selfObj Specifies the object which |this| should + * point to when the function is run. If the value is null or undefined, + * it will default to the global object. + * @param {...*} var_args Additional arguments that are partially + * applied to the function. + * + * @return {!Function} A partially-applied form of the function bind() was + * invoked as a method of. + */ + function bind(fn, selfObj, var_args) { + var boundArgs = Array.prototype.slice.call(arguments, 2); + return function() { + var args = Array.prototype.slice.call(arguments); + args.unshift.apply(args, boundArgs); + return fn.apply(selfObj, args); + } + } + + /** + * Dispatches a simple event on an event target. + * @param {!EventTarget} target The event target to dispatch the event on. + * @param {string} type The type of the event. + * @param {boolean=} opt_bubbles Whether the event bubbles or not. + * @param {boolean=} opt_cancelable Whether the default action of the event + * can be prevented. + * @return {boolean} If any of the listeners called {@code preventDefault} + * during the dispatch this will return false. + */ + function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) { + var e = new cr.Event(type, opt_bubbles, opt_cancelable); + return target.dispatchEvent(e); + } + + /** + * @param {string} name + * @param {!Function} fun + */ + function define(name, fun) { + var obj = exportPath(name); + var exports = fun(); + for (var key in exports) { + obj[key] = exports[key]; + } + } + + /** + * Document used for various document related operations. + * @type {!Document} + */ + var doc = document; + + + /** + * Allows you to run func in the context of a different document. + * @param {!Document} document The document to use. + * @param {function():*} func The function to call. + */ + function withDoc(document, func) { + var oldDoc = doc; + doc = document; + try { + func(); + } finally { + doc = oldDoc; + } + } + + return { + isMac: isMac, + define: define, + defineProperty: defineProperty, + PropertyKind: PropertyKind, + createUid: createUid, + getUid: getUid, + bind: bind, + dispatchSimpleEvent: dispatchSimpleEvent, + dispatchPropertyChange: dispatchPropertyChange, + + /** + * The document that we are currently using. + * @type {!Document} + */ + get doc() { + return doc; + }, + withDoc: withDoc + }; +})(); diff --git a/chrome/browser/resources/shared/js/cr/event.js b/chrome/browser/resources/shared/js/cr/event.js new file mode 100644 index 0000000..a02a1f3 --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/event.js @@ -0,0 +1,43 @@ +// 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 provides a nicer way to create events than what DOM + * provides. These events can be used with DOM EventTarget interfaces as well + * as with {@code cr.EventTarget}. + */ + +cr.define('cr', function() { + + // cr.Event is called CrEvent in here to prevent naming conflicts. We also + // store the original Event in case someone does a global alias of cr.Event. + + const DomEvent = Event; + + /** + * Creates a new event to be used with cr.EventTarget or DOM EventTarget + * objects. + * @param {string} type The name of the event. + * @param {boolean=} opt_bubbles Whether the event bubbles. Default is false. + * @param {boolean=} opt_preventable Whether the default action of the event + * can be prevented. + * @constructor + * @extends {DomEvent} + */ + function CrEvent(type, opt_bubbles, opt_preventable) { + var e = cr.doc.createEvent('Event'); + e.initEvent(type, !!opt_bubbles, !!opt_preventable); + e.__proto__ = CrEvent.prototype; + return e; + } + + CrEvent.prototype = { + __proto__: DomEvent.prototype + }; + + // Export + return { + Event: CrEvent + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/eventtarget.js b/chrome/browser/resources/shared/js/cr/eventtarget.js new file mode 100644 index 0000000..5bcb41d --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/eventtarget.js @@ -0,0 +1,104 @@ +// 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 contains an implementation of the EventTarget interface + * as defined by DOM Level 2 Events. + */ + +cr.define('cr', function() { + + /** + * Creates a new EventTarget. This class implements the DOM level 2 + * EventTarget interface and can be used wherever those are used. + * @constructor + */ + function EventTarget() { + } + + EventTarget.prototype = { + + /** + * Adds an event listener to the target. + * @param {string} type The name of the event. + * @param {!Function|{handleEvent:Function}} handler The handler for the + * event. This is called when the event is dispatched. + */ + addEventListener: function(type, handler) { + if (!this.listeners_) + this.listeners_ = Object.create(null); + if (!(type in this.listeners_)) { + this.listeners_[type] = [handler]; + } else { + var handlers = this.listeners_[type]; + if (handlers.indexOf(handler) < 0) + handlers.push(handler); + } + }, + + /** + * Removes an event listener from the target. + * @param {string} type The name of the event. + * @param {!Function|{handleEvent:Function}} handler The handler for the + * event. + */ + removeEventListener: function(type, handler) { + if (!this.listeners_) + return; + if (type in this.listeners_) { + var handlers = this.listeners_[type]; + var index = handlers.indexOf(handler); + if (index >= 0) { + // Clean up if this was the last listener. + if (handlers.length == 1) + delete this.listeners_[type]; + else + handlers.splice(index, 1); + } + } + }, + + /** + * Dispatches an event and calls all the listeners that are listening to + * the type of the event. + * @param {!cr.event.Event} event The event to dispatch. + * @return {boolean} Whether the default action was prevented. If someone + * calls preventDefault on the event object then this returns false. + */ + dispatchEvent: function(event) { + if (!this.listeners_) + return true; + + // Since we are using DOM Event objects we need to override some of the + // properties and methods so that we can emulate this correctly. + var self = this; + event.__defineGetter__('target', function() { + return self; + }); + event.preventDefault = function() { + this.returnValue = false; + }; + + var type = event.type; + var prevented = 0; + if (type in this.listeners_) { + // Clone to prevent removal during dispatch + var handlers = this.listeners_[type].concat(); + for (var i = 0, handler; handler = handlers[i]; i++) { + if (handler.handleEvent) + prevented |= handler.handleEvent.call(handler, event) === false; + else + prevented |= handler.call(this, event) === false; + } + } + + return !prevented && event.returnValue; + } + }; + + // Export + return { + EventTarget: EventTarget + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/eventtarget_test.html b/chrome/browser/resources/shared/js/cr/eventtarget_test.html new file mode 100644 index 0000000..998e7f1 --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/eventtarget_test.html @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + diff --git a/chrome/browser/resources/shared/js/cr/linkcontroller.js b/chrome/browser/resources/shared/js/cr/linkcontroller.js new file mode 100644 index 0000000..e6241de --- /dev/null +++ b/chrome/browser/resources/shared/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.} 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/shared/js/cr/linkcontroller_test.html b/chrome/browser/resources/shared/js/cr/linkcontroller_test.html new file mode 100644 index 0000000..eb7ebd5 --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/linkcontroller_test.html @@ -0,0 +1,353 @@ + + + + + + + + + + + + + diff --git a/chrome/browser/resources/shared/js/cr/promise.js b/chrome/browser/resources/shared/js/cr/promise.js new file mode 100644 index 0000000..a9d233a --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/promise.js @@ -0,0 +1,213 @@ +// 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 implementes a future promise class. + */ + +cr.define('cr', function() { + + /** + * Sentinel used to mark a value as pending. + */ + const PENDING_VALUE = {}; + + /** + * Creates a future promise. + * @param {*=} opt_value The value to set the promise to. If set completes + * the promise immediately. + * @constructor + */ + function Promise(opt_value) { + /** + * An array of the callbacks. + * @type {!Array.} + * @private + */ + this.callbacks_ = []; + + if (arguments.length > 0) + this.value = opt_value; + } + + Promise.prototype = { + /** + * The current value. + * @type {*} + * @private + */ + value_: PENDING_VALUE, + + /** + * The value of the future promise. Accessing this before the promise has + * been fulfilled will throw an error. If this is set to an exception + * accessing this will throw as well. + * @type {*} + */ + get value() { + return this.done ? this.value_ : undefined; + }, + set value(value) { + if (!this.done) { + this.value_ = value; + for (var i = 0; i < this.callbacks_.length; i++) { + this.callbacks_[i].call(null, value); + } + this.callbacks_.length = 0; + } + }, + + /** + * Whether the future promise has been fulfilled. + * @type {boolean} + */ + get done() { + return this.value_ !== PENDING_VALUE; + }, + + /** + * Adds a listener to the future promise. The function will be called when + * the promise is fulfilled. If the promise is already fullfilled this will + * never call the function. + * @param {!Function} fun The function to call. + */ + addListener: function(fun) { + if (this.done) + fun(this.value); + else + this.callbacks_.push(fun); + }, + + /** + * Removes a previously added listener from the future promise. + * @param {!Function} fun The function to remove. + */ + removeListener: function(fun) { + var i = this.callbacks_.indexOf(fun); + if (i >= 0) + this.callbacks_.splice(i, 1); + }, + + /** + * If the promise is done then this returns the string representation of + * the value. + * @return {string} The string representation of the promise. + * @override + */ + toString: function() { + if (this.done) + return String(this.value); + else + return '[object Promise]'; + }, + + /** + * Override to allow arithmetic. + * @override + */ + valueOf: function() { + return this.value; + } + }; + + /** + * When a future promise is done call {@code fun}. This also calls the + * function if the promise has already been fulfilled. + * @param {!Promise} p The promise. + * @param {!Function} fun The function to call when the promise is fulfilled. + */ + Promise.when = function(p, fun) { + p.addListener(fun); + }; + + /** + * Creates a new promise the will be fulfilled after {@code t} ms. + * @param {number} t The time to wait before the promise is fulfilled. + * @param {*=} opt_value The value to return after the wait. + * @return {!Promise} The new future promise. + */ + Promise.wait = function(t, opt_value) { + var p = new Promise; + window.setTimeout(function() { + p.value = opt_value; + }, t); + return p; + }; + + /** + * Creates a new future promise that is fulfilled when any of the promises are + * 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 the + * passed in promises are fulfilled. + */ + Promise.any = function(var_args) { + var p = new Promise; + function f(v) { + p.value = v; + } + for (var i = 0; i < arguments.length; i++) { + arguments[i].addListener(f); + } + return p; + }; + + /** + * 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. + * @param {boolean=} opt_useCapture Whether to listen to the capture phase or + * the bubble phase. + * @return {!Promise} The promise that will be fulfilled when the event is + * dispatched. + */ + Promise.event = function(target, type, opt_useCapture) { + var p = new Promise; + target.addEventListener(type, function(e) { + p.value = e; + }, opt_useCapture); + return p; + }; + + return { + Promise: Promise + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/promise_test.html b/chrome/browser/resources/shared/js/cr/promise_test.html new file mode 100644 index 0000000..5c9ae1d --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/promise_test.html @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + diff --git a/chrome/browser/resources/shared/js/cr/ui.js b/chrome/browser/resources/shared/js/cr/ui.js new file mode 100644 index 0000000..d87c04a --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui.js @@ -0,0 +1,173 @@ +// 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. + +cr.define('cr.ui', function() { + + /** + * Decorates elements as an instance of a class. + * @param {string|!Element} source The way to find the element(s) to decorate. + * If this is a string then {@code querySeletorAll} is used to find the + * elements to decorate. + * @param {!Function} constr The constructor to decorate with. The constr + * needs to have a {@code decorate} function. + */ + function decorate(source, constr) { + var elements; + if (typeof source == 'string') + elements = cr.doc.querySelectorAll(source); + else + elements = [source]; + + for (var i = 0, el; el = elements[i]; i++) { + if (!(el instanceof constr)) + constr.decorate(el); + } + } + + /** + * Helper function for creating new element for define. + */ + function createElementHelper(tagName, opt_bag) { + // Allow passing in ownerDocument to create in a different document. + var doc; + if (opt_bag && opt_bag.ownerDocument) + doc = opt_bag.ownerDocument; + else + doc = cr.doc; + return doc.createElement(tagName); + } + + /** + * Creates the constructor for a UI element class. + * + * Usage: + *
+   * var List = cr.ui.define('list');
+   * List.prototype = {
+   *   __proto__: HTMLUListElement.prototype,
+   *   decorate: function() {
+   *     ...
+   *   },
+   *   ...
+   * };
+   * 
+ * + * @param {string|Function} tagNameOrFunction The tagName or + * function to use for newly created elements. If this is a function it + * needs to return a new element when called. + * @return {function(Object=):Element} The constructor function which takes + * an optional property bag. The function also has a static + * {@code decorate} method added to it. + */ + function define(tagNameOrFunction) { + var createFunction, tagName; + if (typeof tagNameOrFunction == 'function') { + createFunction = tagNameOrFunction; + tagName = ''; + } else { + createFunction = createElementHelper; + tagName = tagNameOrFunction; + } + + /** + * Creates a new UI element constructor. + * @param {Object=} opt_propertyBag Optional bag of properties to set on the + * object after created. The property {@code ownerDocument} is special + * cased and it allows you to create the element in a different + * document than the default. + * @constructor + */ + function f(opt_propertyBag) { + var el = createFunction(tagName, opt_propertyBag); + f.decorate(el); + for (var propertyName in opt_propertyBag) { + el[propertyName] = opt_propertyBag[propertyName]; + } + return el; + } + + /** + * Decorates an element as a UI element class. + * @param {!Element} el The element to decorate. + */ + f.decorate = function(el) { + el.__proto__ = f.prototype; + el.decorate(); + }; + + return f; + } + + /** + * Input elements do not grow and shrink with their content. This is a simple + * (and not very efficient) way of handling shrinking to content with support + * for min width and limited by the width of the parent element. + * @param {HTMLElement} el The element to limit the width for. + * @param {number} parentEl The parent element that should limit the size. + * @param {number} min The minimum width. + */ + function limitInputWidth(el, parentEl, min) { + // Needs a size larger than borders + el.style.width = '10px'; + var doc = el.ownerDocument; + var win = doc.defaultView; + var computedStyle = win.getComputedStyle(el); + var parentComputedStyle = win.getComputedStyle(parentEl); + var rtl = computedStyle.direction == 'rtl'; + + // To get the max width we get the width of the treeItem minus the position + // of the input. + var inputRect = el.getBoundingClientRect(); // box-sizing + var parentRect = parentEl.getBoundingClientRect(); + var startPos = rtl ? parentRect.right - inputRect.right : + inputRect.left - parentRect.left; + + // Add up border and padding of the input. + var inner = parseInt(computedStyle.borderLeftWidth, 10) + + parseInt(computedStyle.paddingLeft, 10) + + parseInt(computedStyle.paddingRight, 10) + + parseInt(computedStyle.borderRightWidth, 10); + + // We also need to subtract the padding of parent to prevent it to overflow. + var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) : + parseInt(parentComputedStyle.paddingRight, 10); + + // The magic number 14 comes from trial and error :'( It consists of: + // border + padding + treeItem.paddingEnd + treeItem.borderEnd + + // tree.paddingEnd + var max = parentEl.clientWidth - startPos - inner - parentPadding; + + var pcs = getComputedStyle(parentEl); + console.log('pcs', 'borderLeft', pcs.borderLeftWidth, + 'paddingLeft', pcs.paddingLeft, + 'paddingRight', pcs.paddingRight, + 'borderRight', pcs.borderRightWidth, + 'width', pcs.width, + 'clientWidth', parentEl.clientWidth, + 'offsetWidth', parentEl.offsetWidth); + + function limit() { + if (el.scrollWidth > max) { + el.style.width = max + 'px'; + } else { + el.style.width = 0; + var sw = el.scrollWidth; + if (sw < min) { + el.style.width = min + 'px'; + } else { + el.style.width = sw + 'px'; + } + } + } + + el.addEventListener('input', limit); + limit(); + } + + return { + decorate: decorate, + define: define, + limitInputWidth: limitInputWidth + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/command.js b/chrome/browser/resources/shared/js/cr/ui/command.js new file mode 100644 index 0000000..4df2275 --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/command.js @@ -0,0 +1,295 @@ +// 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 A command is an abstraction of an action a user can do in the + * UI. + * + * When the focus changes in the document for each command a canExecute event + * is dispatched on the active element. By listening to this event you can + * enable and disable the command by setting the event.canExecute property. + * + * When a command is executed a command event is dispatched on the active + * element. Note that you should stop the propagation after you have handled the + * command if there might be other command listeners higher up in the DOM tree. + */ + +cr.define('cr.ui', function() { + + /** + * This is used to identify keyboard shortcuts. + * @param {string} shortcut The text used to describe the keys for this + * keyboard shortcut. + * @constructor + */ + function KeyboardShortcut(shortcut) { + var mods = {}; + var ident = ''; + shortcut.split('-').forEach(function(part) { + var partLc = part.toLowerCase(); + switch (partLc) { + case 'alt': + case 'ctrl': + case 'meta': + case 'shift': + mods[partLc + 'Key'] = true; + break; + default: + if (ident) + throw Error('Invalid shortcut'); + ident = part; + } + }); + + this.ident_ = ident; + this.mods_ = mods; + } + + KeyboardShortcut.prototype = { + /** + * Wether the keyboard shortcut object mathes a keyboard event. + * @param {!Event} e The keyboard event object. + * @return {boolean} Whether we found a match or not. + */ + matchesEvent: function(e) { + if (e.keyIdentifier == this.ident_) { + // All keyboard modifiers needs to match. + var mods = this.mods_; + return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) { + return e[k] == !!mods[k]; + }); + } + return false; + } + }; + + /** + * Creates a new command element. + * @constructor + * @extends {HTMLElement} + */ + var Command = cr.ui.define('command'); + + Command.prototype = { + __proto__: HTMLElement.prototype, + + /** + * Initializes the command. + */ + decorate: function() { + CommandManager.init(this.ownerDocument); + }, + + /** + * Executes the command. This dispatches a command event on the active + * element. If the command is {@code disabled} this does nothing. + */ + execute: function() { + if (this.disabled) + return; + var doc = this.ownerDocument; + if (doc.activeElement) { + var e = new cr.Event('command', true, false); + e.command = this; + doc.activeElement.dispatchEvent(e); + } + }, + + /** + * Call this when there have been changes that might change whether the + * command can be executed or not. + */ + canExecuteChange: function() { + dispatchCanExecuteEvent(this, this.ownerDocument.activeElement); + }, + + /** + * The keyboard shortcut that triggers the command. This is a string + * consisting of a keyIdentifier (as reported by WebKit in keydown) as + * well as optional key modifiers joinded with a '-'. + * + * Multiple keyboard shortcuts can be provided by separating them by + * whitespace. + * + * For example: + * "F1" + * "U+0008-Meta" for Apple command backspace. + * "U+0041-Ctrl" for Control A + * "U+007F U+0008-Meta" for Delete and Command Backspace + * + * @type {string} + */ + shortcut_: '', + get shortcut() { + return this.shortcut_; + }, + set shortcut(shortcut) { + var oldShortcut = this.shortcut_; + if (shortcut !== oldShortcut) { + this.keyboardShortcuts_ = shortcut.split(/\s+/).map(function(shortcut) { + return new KeyboardShortcut(shortcut); + }); + + // Set this after the keyboardShortcuts_ since that might throw. + this.shortcut_ = shortcut; + cr.dispatchPropertyChange(this, 'shortcut', this.shortcut_, + oldShortcut); + } + }, + + /** + * Whether the event object matches the shortcut for this command. + * @param {!Event} e The key event object. + * @return {boolean} Whether it matched or not. + */ + matchesEvent: function(e) { + if (!this.keyboardShortcuts_) + return false; + + return this.keyboardShortcuts_.some(function(keyboardShortcut) { + return keyboardShortcut.matchesEvent(e); + }); + } + }; + + /** + * The label of the command. + * @type {string} + */ + cr.defineProperty(Command, 'label', cr.PropertyKind.ATTR); + + /** + * Whether the command is disabled or not. + * @type {boolean} + */ + cr.defineProperty(Command, 'disabled', cr.PropertyKind.BOOL_ATTR); + + /** + * Whether the command is hidden or not. + * @type {boolean} + */ + cr.defineProperty(Command, 'hidden', cr.PropertyKind.BOOL_ATTR); + + /** + * Dispatches a canExecute event on the target. + * @param {cr.ui.Command} command The command that we are testing for. + * @param {Element} target The target element to dispatch the event on. + */ + function dispatchCanExecuteEvent(command, target) { + var e = new CanExecuteEvent(command, true) + target.dispatchEvent(e); + command.disabled = !e.canExecute; + } + + /** + * The command managers for different documents. + */ + var commandManagers = {}; + + /** + * Keeps track of the focused element and updates the commands when the focus + * changes. + * @param {!Document} doc The document that we are managing the commands for. + * @constructor + */ + function CommandManager(doc) { + doc.addEventListener('focus', cr.bind(this.handleFocus_, this), true); + // Make sure we add the listener to the bubbling phase so that elements can + // prevent the command. + doc.addEventListener('keydown', cr.bind(this.handleKeyDown_, this), false); + } + + /** + * Initializes a command manager for the document as needed. + * @param {!Document} doc The document to manage the commands for. + */ + CommandManager.init = function(doc) { + var uid = cr.getUid(doc); + if (!(uid in commandManagers)) { + commandManagers[uid] = new CommandManager(doc); + } + }, + + CommandManager.prototype = { + + /** + * Handles focus changes on the document. + * @param {Event} e The focus event object. + * @private + */ + handleFocus_: function(e) { + var target = e.target; + var commands = Array.prototype.slice.call( + target.ownerDocument.querySelectorAll('command')); + + commands.forEach(function(command) { + dispatchCanExecuteEvent(command, target); + }); + }, + + /** + * Handles the keydown event and routes it to the right command. + * @param {!Event} e The keydown event. + */ + handleKeyDown_: function(e) { + var target = e.target; + var commands = Array.prototype.slice.call( + target.ownerDocument.querySelectorAll('command')); + + for (var i = 0, command; command = commands[i]; i++) { + if (!command.disabled && command.matchesEvent(e)) { + e.preventDefault(); + // We do not want any other element to handle this. + e.stopPropagation(); + + command.execute(); + return; + } + } + } + }; + + /** + * The event type used for canExecute events. + * @param {!cr.ui.Command} command The command that we are evaluating. + * @extends {Event} + */ + function CanExecuteEvent(command) { + var e = command.ownerDocument.createEvent('Event'); + e.initEvent('canExecute', true, false); + e.__proto__ = CanExecuteEvent.prototype; + e.command = command; + return e; + } + + CanExecuteEvent.prototype = { + __proto__: Event.prototype, + + /** + * The current command + * @type {cr.ui.Command} + */ + command: null, + + /** + * Whether the target can execute the command. Setting this also stops the + * propagation. + * @type {boolean} + */ + canExecute_: false, + get canExecute() { + return this.canExecute_; + }, + set canExecute(canExecute) { + this.canExecute_ = canExecute; + this.stopPropagation(); + } + }; + + // Export + return { + Command: Command, + CanExecuteEvent: CanExecuteEvent + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/contextmenuhandler.js b/chrome/browser/resources/shared/js/cr/ui/contextmenuhandler.js new file mode 100644 index 0000000..2d96a4c --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/contextmenuhandler.js @@ -0,0 +1,225 @@ +// 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. + +cr.define('cr.ui', function() { + + /** + * Handles context menus. + * @constructor + */ + function ContextMenuHandler() {} + + ContextMenuHandler.prototype = { + + /** + * The menu that we are currently showing. + * @type {cr.ui.Menu} + */ + menu_: null, + get menu() { + return this.menu_; + }, + + /** + * Shows a menu as a context menu. + * @param {!Event} e The event triggering the show (usally a contextmenu + * event). + * @param {!cr.ui.Menu} menu The menu to show. + */ + showMenu: function(e, menu) { + this.menu_ = menu; + + menu.style.display = 'block'; + // when the menu is shown we steal all keyboard events. + menu.ownerDocument.addEventListener('keydown', this, true); + menu.ownerDocument.addEventListener('mousedown', this, true); + menu.ownerDocument.addEventListener('blur', this, true); + menu.addEventListener('activate', this); + this.positionMenu_(e, menu); + }, + + /** + * Hide the currently shown menu. + */ + hideMenu: function() { + var menu = this.menu; + if (!menu) + return; + + menu.style.display = 'none'; + menu.ownerDocument.removeEventListener('keydown', this, true); + menu.ownerDocument.removeEventListener('mousedown', this, true); + menu.ownerDocument.removeEventListener('blur', this, true); + menu.removeEventListener('activate', this); + menu.selectedIndex = -1; + this.menu_ = null; + + // On windows we might hide the menu in a right mouse button up and if + // that is the case we wait some short period before we allow the menu + // to be shown again. + this.hideTimestamp_ = Date.now(); + }, + + /** + * Positions the menu + * @param {!Event} e The event object triggering the showing. + * @param {!cr.ui.Menu} menu The menu to position. + * @private + */ + positionMenu_: function(e, menu) { + // TODO(arv): Handle scrolled documents when needed. + + var element = e.currentTarget; + var x, y; + // When the user presses the context menu key (on the keyboard) we need + // to detect this. + if (e.screenX == 0 && e.screenY == 0) { + var rect = element.getRectForContextMenu ? + element.getRectForContextMenu() : + element.getBoundingClientRect(); + var offset = Math.min(rect.width, rect.height) / 2; + x = rect.left + offset; + y = rect.top + offset; + } else { + x = e.clientX; + y = e.clientY; + } + + var menuRect = menu.getBoundingClientRect(); + var bodyRect = menu.ownerDocument.body.getBoundingClientRect(); + + // Does menu fit below? + if (y + menuRect.height > bodyRect.height) { + // Does menu fit above? + if (y - menuRect.height >= 0) { + y -= menuRect.height; + } else { + // Menu did not fit above nor below. + y = 0; + // We could resize the menu here but lets not worry about that at this + // point. + } + } + + // Does menu fit to the right? + if (x + menuRect.width > bodyRect.width) { + // Does menu fit to the left? + if (x - menuRect.width >= 0) { + x -= menuRect.width; + } else { + // Menu did not fit to the right nor to the left. + x = 0; + // We could resize the menu here but lets not worry about that at this + // point. + } + } + + menu.style.left = x + 'px'; + menu.style.top = y + 'px'; + }, + + /** + * Handles event callbacks. + * @param {!Event} e The event object. + */ + handleEvent: function(e) { + // Context menu is handled even when we have no menu. + if (e.type != 'contextmenu' && !this.menu) + return; + + switch (e.type) { + case 'mousedown': + if (!this.menu.contains(e.target)) + this.hideMenu(); + else + e.preventDefault(); + break; + case 'keydown': + // keyIdentifier does not report 'Esc' correctly + if (e.keyCode == 27 /* Esc */) { + this.hideMenu(); + + // If the menu is visible we let it handle all the keyboard events. + } else if (this.menu) { + this.menu.handleKeyDown(e); + e.preventDefault(); + e.stopPropagation(); + } + break; + + case 'activate': + case 'blur': + this.hideMenu(); + break; + + case 'contextmenu': + if ((!this.menu || !this.menu.contains(e.target)) && + (!this.hideTimestamp_ || Date.now() - this.hideTimestamp_ > 50)) + this.showMenu(e, e.currentTarget.contextMenu); + e.preventDefault(); + // Don't allow elements further up in the DOM to show their menus. + e.stopPropagation(); + break; + } + }, + + /** + * Adds a contextMenu property to an element or element class. + * @param {!Element|!Function} element The element or class to add the + * contextMenu property to. + */ + addContextMenuProperty: function(element) { + if (typeof element == 'function') + element = element.prototype; + + element.__defineGetter__('contextMenu', function() { + return this.contextMenu_; + }); + element.__defineSetter__('contextMenu', function(menu) { + var oldContextMenu = this.contextMenu; + + if (typeof menu == 'string' && menu[0] == '#') { + menu = this.ownerDocument.getElementById(menu.slice(1)); + cr.ui.decorate(menu, Menu); + } + + if (menu === oldContextMenu) + return; + + if (oldContextMenu && !menu) + this.removeEventListener('contextmenu', contextMenuHandler); + if (menu && !oldContextMenu) + this.addEventListener('contextmenu', contextMenuHandler); + + this.contextMenu_ = menu; + + if (menu && menu.id) + this.setAttribute('contextmenu', '#' + menu.id); + + cr.dispatchPropertyChange(this, 'contextMenu', menu, oldContextMenu); + }); + + if (!element.getRectForContextMenu) { + /** + * @return {!ClientRect} The rect to use for positioning the context + * menu when the context menu is not opened using a mouse position. + */ + element.getRectForContextMenu = function() { + return this.getBoundingClientRect(); + }; + } + } + }; + + /** + * The singleton context menu handler. + * @type {!ContextMenuHandler} + */ + var contextMenuHandler = new ContextMenuHandler; + + // Export + return { + contextMenuHandler: contextMenuHandler + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/list.js b/chrome/browser/resources/shared/js/cr/ui/list.js new file mode 100644 index 0000000..f221279 --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/list.js @@ -0,0 +1,265 @@ +// 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. + +// require: listselectionmodel.js + +/** + * @fileoverview This implements a list control. + */ + +cr.define('cr.ui', function() { + const ListSelectionModel = cr.ui.ListSelectionModel; + + /** + * Whether a mouse event is inside the element viewport. This will return + * false if the mouseevent was generated over a border or a scrollbar. + * @param {!HTMLElement} el The element to test the event with. + * @param {!Event} e The mouse event. + * @param {boolean} Whether the mouse event was inside the viewport. + */ + function inViewport(el, e) { + var rect = el.getBoundingClientRect(); + var x = e.clientX; + var y = e.clientY; + return x >= rect.left + el.clientLeft && + x < rect.left + el.clientLeft + el.clientWidth && + y >= rect.top + el.clientTop && + y < rect.top + el.clientTop + el.clientHeight; + } + + /** + * Creates a new list element. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLUListElement} + */ + var List = cr.ui.define('list'); + + List.prototype = { + __proto__: HTMLUListElement.prototype, + + /** + * The selection model to use. + * @type {cr.ui.ListSelectionModel} + */ + get selectionModel() { + return this.selectionModel_; + }, + set selectionModel(sm) { + var oldSm = this.selectionModel_; + if (oldSm == sm) + return; + + if (!this.boundHandleOnChange_) { + this.boundHandleOnChange_ = cr.bind(this.handleOnChange_, this); + this.boundHandleLeadChange_ = cr.bind(this.handleLeadChange_, this); + } + + if (oldSm) { + oldSm.removeEventListener('change', this.boundHandleOnChange_); + oldSm.removeEventListener('leadItemChange', this.boundHandleLeadChange_); + } + + this.selectionModel_ = sm; + + if (sm) { + sm.addEventListener('change', this.boundHandleOnChange_); + sm.addEventListener('leadItemChange', this.boundHandleLeadChange_); + } + }, + + /** + * Convenience alias for selectionModel.selectedItem + * @type {cr.ui.ListItem} + */ + get selectedItem() { + return this.selectionModel.selectedItem; + }, + set selectedItem(selectedItem) { + this.selectionModel.selectedItem = selectedItem; + }, + + /** + * Convenience alias for selectionModel.selectedItems + * @type {!Array} + */ + get selectedItems() { + return this.selectionModel.selectedItems; + }, + + /** + * The HTML elements representing the items. This is just all the element + * children but subclasses may override this to filter out certain elements. + * @type {HTMLCollection} + */ + get items() { + return this.children; + }, + + batchCount_: 0, + + /** + * When adding a large collection of items to the list, the code should be + * wrapped in the startBatchAdd and startBatchEnd to increase performance. + * This hides the list while it is being built, and prevents it from + * incurring measurement performance hits in between each item. + * Be sure that the code will not return without calling finishBatchAdd + * or the list will not be shown. + * @private + */ + startBatchAdd: function() { + // If we're already in a batch, don't overwrite original display style. + if (this.batchCount_ == 0) { + this.originalDisplayStyle_ = this.style.display; + this.style.display = 'none'; + } + this.batchCount_++; + }, + + /** + * See startBatchAdd. + * @private + */ + finishBatchAdd: function() { + this.batchCount_--; + if (this.batchCount_ == 0) { + this.style.display = this.originalDisplayStyle_; + delete this.originalDisplayStyle; + } + }, + + add: function(listItem) { + this.appendChild(listItem); + + var uid = cr.getUid(listItem); + this.uidToListItem_[uid] = listItem; + + this.selectionModel.add(listItem); + }, + + addAt: function(listItem, index) { + this.insertBefore(listItem, this.items[index]); + + var uid = cr.getUid(listItem); + this.uidToListItem_[uid] = listItem; + + this.selectionModel.add(listItem); + }, + + remove: function(listItem) { + this.selectionModel.remove(listItem); + + this.removeChild(listItem); + + var uid = cr.getUid(listItem); + delete this.uidToListItem_[uid]; + }, + + clear: function() { + this.innerHTML = ''; + this.selectionModel.clear(); + }, + + /** + * Initializes the element. + */ + decorate: function() { + this.uidToListItem_ = {}; + + this.selectionModel = new ListSelectionModel(this); + + this.addEventListener('mousedown', this.handleMouseDownUp_); + this.addEventListener('mouseup', this.handleMouseDownUp_); + this.addEventListener('keydown', this.handleKeyDown); + + // Make list focusable + if (!this.hasAttribute('tabindex')) + this.tabIndex = 0; + }, + + /** + * Callback for mousedown and mouseup events. + * @param {Event} e The mouse event object. + * @private + */ + handleMouseDownUp_: function(e) { + var target = e.target; + + // If the target was this element we need to make sure that the user did + // not click on a border or a scrollbar. + if (target == this && !inViewport(target, e)) + return; + + while (target && target.parentNode != this) { + target = target.parentNode; + } + + this.selectionModel.handleMouseDownUp(e, target); + }, + + /** + * Handle a keydown event. + * @param {Event} e The keydown event. + * @return {boolean} Whether the key event was handled. + */ + handleKeyDown: function(e) { + return this.selectionModel.handleKeyDown(e); + }, + + /** + * Callback from the selection model. We dispatch {@code change} events + * when the selection changes. + * @param {!cr.Event} e Event with change info. + * @private + */ + handleOnChange_: function(ce) { + ce.changes.forEach(function(change) { + var listItem = this.uidToListItem_[change.uid]; + listItem.selected = change.selected; + }, this); + + cr.dispatchSimpleEvent(this, 'change'); + }, + + /** + * Handles a change of the lead item from the selection model. + * @property {Event} pe The property change event. + * @private + */ + handleLeadChange_: function(pe) { + if (pe.oldValue) { + pe.oldValue.lead = false; + } + if (pe.newValue) { + pe.newValue.lead = true; + } + }, + + /** + * Gets a unique ID for an item. This needs to be unique to the list but + * does not have to be gloabally unique. This uses {@code cr.getUid} by + * default. Override to provide a more efficient way to get the unique ID. + * @param {cr.ui.ListItem} item The item to get the unique ID for. + * @return + */ + itemToUid: function(item) { + return cr.getUid(item); + }, + + /** + * @return {!ClientRect} The rect to use for the context menu. + */ + getRectForContextMenu: function() { + // TODO(arv): Add trait support so we can share more code between trees + // and lists. + if (this.selectedItem) + return this.selectedItem.getBoundingClientRect(); + return this.getBoundingClientRect(); + } + }; + + return { + List: List + } +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/listitem.js b/chrome/browser/resources/shared/js/cr/ui/listitem.js new file mode 100644 index 0000000..0cd8826 --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/listitem.js @@ -0,0 +1,58 @@ +// 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. + +cr.define('cr.ui', function() { + + /** + * Creates a new list item element. + * @param {string} opt_label The text label for the item. + * @constructor + * @extends {HTMLLIElement} + */ + var ListItem = cr.ui.define('li'); + + ListItem.prototype = { + __proto__: HTMLLIElement.prototype, + + /** + * Plain text label. + * @type {string} + */ + get label() { + return this.textContent; + }, + set label(label) { + this.textContent = label; + }, + + /** + * Whether the item is the lead in a selection. Setting this does not update + * the underlying selection model. This is only used for display purpose. + * @type {boolean} + */ + get lead() { + return this.hasAttribute('lead'); + }, + set lead(lead) { + if (lead) { + this.setAttribute('lead', ''); + this.scrollIntoViewIfNeeded(false); + } else { + this.removeAttribute('lead'); + } + }, + + /** + * Called when an element is decorated as a list item. + */ + decorate: function() { + } + }; + + cr.defineProperty(ListItem, 'selected', cr.PropertyKind.BOOL_ATTR); + + return { + ListItem: ListItem + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/listselectionmodel.js b/chrome/browser/resources/shared/js/cr/ui/listselectionmodel.js new file mode 100644 index 0000000..ef9bce8 --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/listselectionmodel.js @@ -0,0 +1,450 @@ +// 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. + +cr.define('cr.ui', function() { + const Event = cr.Event; + const EventTarget = cr.EventTarget; + + /** + * Creates a new selection model that is to be used with lists. This is + * implemented for vertical lists but changing the behavior for horizontal + * lists or icon views is a matter of overriding {@code getItemBefore}, + * {@code getItemAfter}, {@code getItemAbove} as well as {@code getItemBelow}. + * + * @constructor + * @extends {!cr.EventTarget} + */ + function ListSelectionModel(list) { + this.list = list; + this.selectedItems_ = {}; + } + + ListSelectionModel.prototype = { + __proto__: EventTarget.prototype, + + /** + * Returns the item below (y axis) the given element. + * @param {*} item The item to get the item below. + * @return {*} The item below or null if not found. + */ + getItemBelow: function(item) { + return item.nextElementSibling; + }, + + /** + * Returns the item above (y axis) the given element. + * @param {*} item The item to get the item above. + * @return {*} The item below or null if not found. + */ + getItemAbove: function(item) { + return item.previousElementSibling; + }, + + /** + * Returns the item before (x axis) the given element. This returns null + * by default but override this for icon view and horizontal selection + * models. + * + * @param {*} item The item to get the item before. + * @return {*} The item before or null if not found. + */ + getItemBefore: function(item) { + return null; + }, + + /** + * Returns the item after (x axis) the given element. This returns null + * by default but override this for icon view and horizontal selection + * models. + * + * @param {*} item The item to get the item after. + * @return {*} The item after or null if not found. + */ + getItemAfter: function(item) { + return null; + }, + + /** + * Returns the next list item. This is the next logical and should not + * depend on any kind of layout of the list. + * @param {*} item The item to get the next item for. + * @return {*} The next item or null if not found. + */ + getNextItem: function(item) { + return item.nextElementSibling; + }, + + /** + * Returns the prevous list item. This is the previous logical and should + * not depend on any kind of layout of the list. + * @param {*} item The item to get the previous item for. + * @return {*} The previous item or null if not found. + */ + getPreviousItem: function(item) { + return item.previousElementSibling; + }, + + /** + * @return {*} The first item. + */ + getFirstItem: function() { + return this.list.firstElementChild; + }, + + /** + * @return {*} The last item. + */ + getLastItem: function() { + return this.list.lastElementChild; + }, + + /** + * Called by the view when the user does a mousedown or mouseup on the list. + * @param {!Event} e The browser mousedown event. + * @param {*} item The item that was under the mouse pointer, null if none. + */ + handleMouseDownUp: function(e, item) { + var anchorItem = this.anchorItem; + var isDown = e.type == 'mousedown'; + + this.beginChange_(); + + if (!item) { + // On Mac we always clear the selection if the user clicks a blank area. + // On Windows, we only clear the selection if neither Shift nor Ctrl are + // pressed. + if (cr.isMac) { + this.clear(); + } else if (!isDown && !e.shiftKey && !e.ctrlKey) + // Keep anchor and lead items. + this.clearAllSelected_(); + } else { + if (cr.isMac ? e.metaKey : e.ctrlKey) { + // Selection is handled at mouseUp on windows/linux, mouseDown on mac. + if (cr.isMac? isDown : !isDown) { + // toggle the current one and make it anchor item + this.setItemSelected(item, !this.getItemSelected(item)); + this.leadItem = item; + this.anchorItem = item; + } + } else if (e.shiftKey && anchorItem && anchorItem != item) { + // Shift is done in mousedown + if (isDown) { + this.clearAllSelected_(); + this.leadItem = item; + this.selectRange(anchorItem, item); + } + } else { + // Right click for a context menu need to not clear the selection. + var isRightClick = e.button == 2; + + // If the item is selected this is handled in mouseup. + var itemSelected = this.getItemSelected(item); + if ((itemSelected && !isDown || !itemSelected && isDown) && + !(itemSelected && isRightClick)) { + this.clearAllSelected_(); + this.setItemSelected(item, true); + this.leadItem = item; + this.anchorItem = item; + } + } + } + + this.endChange_(); + }, + + /** + * Called by the view when it recieves a keydown event. + * @param {Event} e The keydown event. + */ + handleKeyDown: function(e) { + var newItem = null; + var leadItem = this.leadItem; + var prevent = true; + + // Ctrl/Meta+A + if (e.keyCode == 65 && + (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) { + this.selectAll(); + e.preventDefault(); + return; + } + + // Space + if (e.keyCode == 32) { + if (leadItem != null) { + var selected = this.getItemSelected(leadItem); + if (e.ctrlKey || !selected) { + this.beginChange_(); + this.setItemSelected(leadItem, !selected); + this.endChange_(); + return; + } + } + } + + switch (e.keyIdentifier) { + case 'Home': + newItem = this.getFirstItem(); + break; + case 'End': + newItem = this.getLastItem(); + break; + case 'Up': + newItem = !leadItem ? + this.getLastItem() : this.getItemAbove(leadItem); + break; + case 'Down': + newItem = !leadItem ? + this.getFirstItem() : this.getItemBelow(leadItem); + break; + case 'Left': + newItem = !leadItem ? + this.getLastItem() : this.getItemBefore(leadItem); + break; + case 'Right': + newItem = !leadItem ? + this.getFirstItem() : this.getItemAfter(leadItem); + break; + default: + prevent = false; + } + + if (newItem) { + this.beginChange_(); + + this.leadItem = newItem; + if (e.shiftKey) { + var anchorItem = this.anchorItem; + this.clearAllSelected_(); + if (!anchorItem) { + this.setItemSelected(newItem, true); + this.anchorItem = newItem; + } else { + this.selectRange(anchorItem, newItem); + } + } else if (e.ctrlKey && !cr.isMac) { + // Setting the lead item is done above + // Mac does not allow you to change the lead. + } else { + this.clearAllSelected_(); + this.setItemSelected(newItem, true); + this.anchorItem = newItem; + } + + this.endChange_(); + + if (prevent) + e.preventDefault(); + } + }, + + /** + * @type {!Array} The selected items. + */ + get selectedItems() { + return Object.keys(this.selectedItems_).map(function(uid) { + return this.selectedItems_[uid]; + }, this); + }, + set selectedItems(selectedItems) { + this.beginChange_(); + this.clearAllSelected_(); + for (var i = 0; i < selectedItems.length; i++) { + this.setItemSelected(selectedItems[i], true); + } + this.leadItem = this.anchorItem = selectedItems[0] || null; + this.endChange_(); + }, + + /** + * Convenience getter which returns the first selected item. + * @type {*} + */ + get selectedItem() { + for (var uid in this.selectedItems_) { + return this.selectedItems_[uid]; + } + return null; + }, + set selectedItem(selectedItem) { + this.beginChange_(); + this.clearAllSelected_(); + if (selectedItem) { + this.selectedItems = [selectedItem]; + } else { + this.leadItem = this.anchorItem = null; + } + this.endChange_(); + }, + + /** + * Selects a range of items, starting with {@code start} and ends with + * {@code end}. + * @param {*} start The first item to select. + * @param {*} end The last item to select. + */ + selectRange: function(start, end) { + // Swap if starts comes after end. + if (start.compareDocumentPosition(end) & Node.DOCUMENT_POSITION_PRECEDING) { + var tmp = start; + start = end; + end = tmp; + } + + this.beginChange_(); + + for (var item = start; item != end; item = this.getNextItem(item)) { + this.setItemSelected(item, true); + } + this.setItemSelected(end, true); + + this.endChange_(); + }, + + /** + * Selects all items. + */ + selectAll: function() { + this.selectRange(this.getFirstItem(), this.getLastItem()); + }, + + /** + * Clears the selection + */ + clear: function() { + this.beginChange_(); + this.anchorItem = this.leadItem = null; + this.clearAllSelected_(); + this.endChange_(); + }, + + /** + * Clears the selection and updates the view. + * @private + */ + clearAllSelected_: function() { + for (var uid in this.selectedItems_) { + this.setItemSelected(this.selectedItems_[uid], false); + } + }, + + /** + * Sets the selecte state for an item. + * @param {*} item The item to set the selected state for. + * @param {boolean} b Whether to select the item or not. + */ + setItemSelected: function(item, b) { + var uid = this.list.itemToUid(item); + var oldSelected = uid in this.selectedItems_; + if (oldSelected == b) + return; + + if (b) + this.selectedItems_[uid] = item; + else + delete this.selectedItems_[uid]; + + this.beginChange_(); + + // Changing back? + if (uid in this.changedUids_ && this.changedUids_[uid] == !b) { + delete this.changedUids_[uid]; + } else { + this.changedUids_[uid] = b; + } + + // End change dispatches an event which in turn may update the view. + this.endChange_(); + }, + + /** + * Whether a given item is selected or not. + * @param {*} item The item to check. + * @return {boolean} Whether an item is selected. + */ + getItemSelected: function(item) { + var uid = this.list.itemToUid(item); + return uid in this.selectedItems_; + }, + + /** + * This is used to begin batching changes. Call {@code endChange_} when you + * are done making changes. + * @private + */ + beginChange_: function() { + if (!this.changeCount_) { + this.changeCount_ = 0; + this.changedUids_ = {}; + } + this.changeCount_++; + }, + + /** + * Call this after changes are done and it will dispatch a change event if + * any changes were actually done. + * @private + */ + endChange_: function() { + this.changeCount_--; + if (!this.changeCount_) { + var uids = Object.keys(this.changedUids_); + if (uids.length) { + var e = new Event('change'); + e.changes = uids.map(function(uid) { + return { + uid: uid, + selected: this.changedUids_[uid] + }; + }, this); + this.dispatchEvent(e); + } + delete this.changedUids_; + delete this.changeCount_; + } + }, + + /** + * Called when an item is removed from the lisst. + * @param {cr.ui.ListItem} item The list item that was removed. + */ + remove: function(item) { + if (item == this.leadItem) + this.leadItem = this.getNextItem(item) || this.getPreviousItem(item); + if (item == this.anchorItem) + this.anchorItem = this.getNextItem(item) || this.getPreviousItem(item); + + // Deselect when removing items. + if (this.getItemSelected(item)) + this.setItemSelected(item, false); + }, + + /** + * Called when an item was added to the list. + * @param {cr.ui.ListItem} item The list item to add. + */ + add: function(item) { + // We could (should?) check if the item is selected here and update the + // selection model. + } + }; + + /** + * The anchorItem is used with multiple selection. + * @type {*} + */ + cr.defineProperty(ListSelectionModel, 'anchorItem', cr.PropertyKind.JS, null); + + /** + * The leadItem is used with multiple selection and it is the item that the + * user is moving uysing the arrow keys. + * @type {*} + */ + cr.defineProperty(ListSelectionModel, 'leadItem', cr.PropertyKind.JS, null); + + return { + ListSelectionModel: ListSelectionModel + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/menu.js b/chrome/browser/resources/shared/js/cr/ui/menu.js new file mode 100644 index 0000000..1145d0f --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/menu.js @@ -0,0 +1,157 @@ +// 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. + +cr.define('cr.ui', function() { + + const MenuItem = cr.ui.MenuItem; + + /** + * Creates a new menu element. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLMenuElement} + */ + var Menu = cr.ui.define('menu'); + + Menu.prototype = { + __proto__: HTMLMenuElement.prototype, + + /** + * Initializes the menu element. + */ + decorate: function() { + this.addEventListener('mouseover', this.handleMouseOver_); + this.addEventListener('mouseout', this.handleMouseOut_); + + // Decorate the children as menu items. + var children = this.children; + for (var i = 0, child; child = children[i]; i++) { + cr.ui.decorate(child, MenuItem); + } + }, + + /** + * Walks up the ancestors until a menu item belonging to this menu is found. + * @param {Element} el + * @return {cr.ui.MenuItem} The found menu item or null. + * @private + */ + findMenuItem_: function(el) { + while (el && el.parentNode != this) { + el = el.parentNode; + } + return el; + }, + + /** + * Handles mouseover events and selects the hovered item. + * @param {Event} e The mouseover event. + * @private + */ + handleMouseOver_: function(e) { + var overItem = this.findMenuItem_(e.target); + this.selectedItem = overItem; + }, + + /** + * Handles mouseout events and deselects any selected item. + * @param {Event} e The mouseout event. + * @private + */ + handleMouseOut_: function(e) { + this.selectedItem = null; + }, + + /** + * The index of the selected item. + * @type {boolean} + */ + // getter and default value is defined using cr.defineProperty. + set selectedIndex(selectedIndex) { + if (this.selectedIndex_ != selectedIndex) { + var oldSelectedItem = this.selectedItem; + this.selectedIndex_ = selectedIndex; + if (oldSelectedItem) + oldSelectedItem.selected = false; + var item = this.selectedItem; + if (item) + item.selected = true; + + cr.dispatchSimpleEvent(this, 'change'); + } + }, + + /** + * The selected menu item or null if none. + * @type {cr.ui.MenuItem} + */ + get selectedItem() { + return this.children[this.selectedIndex]; + }, + set selectedItem(item) { + var index = Array.prototype.indexOf.call(this.children, item); + this.selectedIndex = index; + }, + + /** + * This is the function that handles keyboard navigation. This is usually + * called by the element responsible for managing the menu. + * @param {Event} e The keydown event object. + * @return {boolean} Whether the event was handled be the menu. + */ + handleKeyDown: function(e) { + var item = this.selectedItem; + + var self = this; + function selectNextVisible(m) { + var children = self.children; + var len = children.length; + var i = self.selectedIndex; + if (i == -1 && m == -1) { + // Edge case when we need to go the last item fisrt. + i = 0; + } + while (true) { + i = (i + m + len) % len; + item = children[i]; + if (item && !item.isSeparator() && !item.hidden) + break; + } + if (item) + self.selectedIndex = i; + } + + switch (e.keyIdentifier) { + case 'Down': + selectNextVisible(1); + return true; + case 'Up': + selectNextVisible(-1); + return true; + case 'Enter': + case 'U+0020': // Space + if (item) { + if (cr.dispatchSimpleEvent(item, 'activate', true, true)) { + if (item.command) + item.command.execute(); + } + } + return true; + } + + return false; + } + }; + + /** + * The selected menu item. + * @type {number} + */ + cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS, -1); + + // Export + return { + Menu: Menu + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/menubutton.js b/chrome/browser/resources/shared/js/cr/ui/menubutton.js new file mode 100644 index 0000000..cd8defb --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/menubutton.js @@ -0,0 +1,167 @@ +// 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. + +cr.define('cr.ui', function() { + const Menu = cr.ui.Menu; + + /** + * Creates a new menu button element. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLButtonElement} + */ + var MenuButton = cr.ui.define('button'); + + MenuButton.prototype = { + __proto__: HTMLButtonElement.prototype, + + /** + * Initializes the menu button. + */ + decorate: function() { + this.addEventListener('mousedown', this); + this.addEventListener('keydown', this); + + var menu; + if ((menu = this.getAttribute('menu'))) + this.menu = menu; + }, + + /** + * The menu associated with the menu button. + * @type {cr.ui.Menu} + */ + get menu() { + return this.menu_; + }, + set menu(menu) { + if (typeof menu == 'string' && menu[0] == '#') { + menu = this.ownerDocument.getElementById(menu.slice(1)); + cr.ui.decorate(menu, Menu); + } + + this.menu_ = menu; + if (menu) { + if (menu.id) + this.setAttribute('menu', '#' + menu.id); + } + }, + + /** + * Handles event callbacks. + * @param {Event} e The event object. + */ + handleEvent: function(e) { + if (!this.menu) + return; + + switch (e.type) { + case 'mousedown': + if (e.currentTarget == this.ownerDocument) { + if (!this.contains(e.target) && !this.menu.contains(e.target)) + this.hideMenu(); + else + e.preventDefault(); + } else { + if (this.isMenuShown()) { + this.hideMenu(); + } else if (e.button == 0) { // Only show the menu when using left + // mouse button. + this.showMenu(); + // Prevent the button from stealing focus on mousedown. + e.preventDefault(); + } + } + break; + case 'keydown': + this.handleKeyDown(e); + // If the menu is visible we let it handle all the keyboard events. + if (this.isMenuShown() && e.currentTarget == this.ownerDocument) { + this.menu.handleKeyDown(e); + e.preventDefault(); + e.stopPropagation(); + } + break; + + case 'activate': + case 'blur': + this.hideMenu(); + break; + } + }, + + /** + * Shows the menu. + */ + showMenu: function() { + this.menu.style.display = 'block'; + // when the menu is shown we steal all keyboard events. + this.ownerDocument.addEventListener('keydown', this, true); + this.ownerDocument.addEventListener('mousedown', this, true); + this.ownerDocument.addEventListener('blur', this, true); + this.menu.addEventListener('activate', this); + this.positionMenu_(); + }, + + /** + * Hides the menu. + */ + hideMenu: function() { + this.menu.style.display = 'none'; + this.ownerDocument.removeEventListener('keydown', this, true); + this.ownerDocument.removeEventListener('mousedown', this, true); + this.ownerDocument.removeEventListener('blur', this, true); + this.menu.removeEventListener('activate', this); + this.menu.selectedIndex = -1; + }, + + /** + * Whether the menu is shown. + */ + isMenuShown: function() { + return window.getComputedStyle(this.menu).display != 'none'; + }, + + /** + * Positions the menu below the menu button. At this point we do not use any + * advanced positioning logic to ensure the menu fits in the viewport. + * @private + */ + positionMenu_: function() { + var buttonRect = this.getBoundingClientRect(); + this.menu.style.top = buttonRect.bottom + 'px'; + if (getComputedStyle(this).direction == 'rtl') { + var menuRect = this.menu.getBoundingClientRect(); + this.menu.style.left = buttonRect.right - menuRect.width + 'px'; + } else { + this.menu.style.left = buttonRect.left + 'px'; + } + }, + + /** + * Handles the keydown event for the menu button. + */ + handleKeyDown: function(e) { + switch (e.keyIdentifier) { + case 'Down': + case 'Up': + case 'Enter': + case 'U+0020': // Space + if (!this.isMenuShown()) + this.showMenu(); + e.preventDefault(); + break; + case 'Esc': + case 'U+001B': // Maybe this is remote desktop playing a prank? + this.hideMenu(); + break; + } + } + }; + + // Export + return { + MenuButton: MenuButton + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/menuitem.js b/chrome/browser/resources/shared/js/cr/ui/menuitem.js new file mode 100644 index 0000000..5c66f17 --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/menuitem.js @@ -0,0 +1,147 @@ +// 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. + +cr.define('cr.ui', function() { + const Command = cr.ui.Command; + + /** + * Creates a new menu item element. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLButtonElement} + */ + var MenuItem = cr.ui.define('button'); + + /** + * Creates a new menu separator element. + * @return {cr.ui.MenuItem} + */ + MenuItem.createSeparator = function() { + var el = cr.doc.createElement('hr'); + MenuItem.decorate(el); + return el; + }; + + MenuItem.prototype = { + __proto__: HTMLButtonElement.prototype, + + /** + * Initializes the menu item. + */ + decorate: function() { + var commandId; + if ((commandId = this.getAttribute('command'))) + this.command = commandId; + + this.addEventListener('mouseup', this.handleMouseUp_); + }, + + /** + * The command associated with this menu item. If this is set to a string + * of the form "#element-id" then the element is looked up in the document + * of the command. + * @type {cr.ui.Command} + */ + command_: null, + get command() { + return this.command_; + }, + set command(command) { + if (this.command_) { + this.command_.removeEventListener('labelChange', this); + this.command_.removeEventListener('disabledChange', this); + this.command_.removeEventListener('hiddenChange', this); + } + + if (typeof command == 'string' && command[0] == '#') { + command = this.ownerDocument.getElementById(command.slice(1)); + cr.ui.decorate(command, Command); + } + + this.command_ = command; + if (command) { + if (command.id) + this.setAttribute('command', '#' + command.id); + + this.label = command.label; + this.disabled = command.disabled; + this.hidden = command.hidden; + + this.command_.addEventListener('labelChange', this); + this.command_.addEventListener('disabledChange', this); + this.command_.addEventListener('hiddenChange', this); + } + }, + + /** + * The text label. + * @type {string} + */ + get label() { + return this.textContent; + }, + set label(label) { + this.textContent = label; + }, + + /** + * @return {boolean} Whether the menu item is a separator. + */ + isSeparator: function() { + return this.tagName == 'HR'; + }, + + /** + * Handles mouseup events. This dispatches an active event and if there + * is an assiciated command then that is executed. + * @param {Event} The mouseup event object. + * @private + */ + handleMouseUp_: function(e) { + if (!this.disabled && !this.isSeparator()) { + // Dispatch command event followed by executing the command object. + if (cr.dispatchSimpleEvent(this, 'activate', true, true)) { + var command = this.command; + if (command) + command.execute(); + } + } + }, + + /** + * Handles changes to the associated command. + * @param {Event} e The event object. + */ + handleEvent: function(e) { + switch (e.type) { + case 'disabledChange': + this.disabled = this.command.disabled; + break; + case 'hiddenChange': + this.hidden = this.command.hidden; + break; + case 'labelChange': + this.label = this.command.label; + break; + } + } + }; + + /** + * Whether the menu item is hidden or not. + * @type {boolean} + */ + cr.defineProperty(MenuItem, 'hidden', cr.PropertyKind.BOOL_ATTR); + + /** + * Whether the menu item is selected or not. + * @type {boolean} + */ + cr.defineProperty(MenuItem, 'selected', cr.PropertyKind.BOOL_ATTR); + + // Export + return { + MenuItem: MenuItem + }; +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/splitter.js b/chrome/browser/resources/shared/js/cr/ui/splitter.js new file mode 100644 index 0000000..4f9510e --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/splitter.js @@ -0,0 +1,154 @@ +// 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 implements a splitter element which can be used to resize + * elements in split panes. + * + * The parent of the splitter should be an hbox (display: -webkit-box) with at + * least one previous element sibling. The splitter controls the width of the + * element before it. + * + *
+ *
...
+ *
+ * ... + *
+ * + */ + +cr.define('cr.ui', function() { + // TODO(arv): Currently this only supports horizontal layout. + // TODO(arv): This ignores min-width and max-width of the elements to the + // right of the splitter. + + /** + * Returns the computed style width of an element. + * @param {!Element} el The element to get the width of. + * @return {number} The width in pixels. + */ + function getComputedWidth(el) { + return parseFloat(el.ownerDocument.defaultView.getComputedStyle(el).width) / + getZoomFactor(el.ownerDocument); + } + + /** + * This uses a WebKit bug to work around the same bug. getComputedStyle does + * not take the page zoom into account so it returns the physical pixels + * instead of the logical pixel size. + * @param {!Document} doc The document to get the page zoom factor for. + * @param {number} The zoom factor of the document. + */ + function getZoomFactor(doc) { + var dummyElement = doc.createElement('div'); + dummyElement.style.cssText = + 'position:absolute;width:100px;height:100px;top:-1000px;overflow:hidden'; + doc.body.appendChild(dummyElement); + var cs = doc.defaultView.getComputedStyle(dummyElement); + var rect = dummyElement.getBoundingClientRect(); + var zoomFactor = parseFloat(cs.width) / 100; + doc.body.removeChild(dummyElement); + return zoomFactor; + } + + /** + * Creates a new splitter element. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLDivElement} + */ + var Splitter = cr.ui.define('div'); + + Splitter.prototype = { + __proto__: HTMLDivElement.prototype, + + /** + * Initializes the element. + */ + decorate: function() { + this.addEventListener('mousedown', cr.bind(this.handleMouseDown_, this), + true); + }, + + /** + * Starts the dragging of the splitter. + * @param {!Event} e The mouse event that started the drag. + */ + startDrag: function(e) { + if (!this.boundHandleMouseMove_) { + this.boundHandleMouseMove_ = cr.bind(this.handleMouseMove_, this); + this.boundHandleMouseUp_ = cr.bind(this.handleMouseUp_, this); + } + + var doc = this.ownerDocument; + + // Use capturing events on the document to get events when the mouse + // leaves the document. + doc.addEventListener('mousemove',this.boundHandleMouseMove_, true); + doc.addEventListener('mouseup', this.boundHandleMouseUp_, true); + + // Use the computed width style as the base so that we can ignore what + // box sizing the element has. + var leftComponent = this.previousElementSibling; + var computedWidth = getComputedWidth(leftComponent); + this.startX_ = e.clientX; + this.startWidth_ = computedWidth + }, + + /** + * Ends the dragging of the splitter. This fires a "resize" event if the + * size changed. + */ + endDrag: function() { + var doc = this.ownerDocument; + doc.removeEventListener('mousemove', this.boundHandleMouseMove_, true); + doc.removeEventListener('mouseup', this.boundHandleMouseUp_, true); + + // Check if the size changed. + var leftComponent = this.previousElementSibling; + var computedWidth = getComputedWidth(leftComponent); + if (this.startWidth_ != computedWidth) + cr.dispatchSimpleEvent(this, 'resize'); + }, + + /** + * Handles the mousedown event which starts the dragging of the splitter. + * @param {!Event} e The mouse event. + * @private + */ + handleMouseDown_: function(e) { + this.startDrag(e); + // Default action is to start selection and to move focus. + e.preventDefault(); + }, + + /** + * Handles the mousemove event which moves the splitter as the user moves + * the mouse. + * @param {!Event} e The mouse event. + * @private + */ + handleMouseMove_: function(e) { + var leftComponent = this.previousElementSibling; + var rtl = this.ownerDocument.defaultView.getComputedStyle(this). + direction == 'rtl'; + var dirMultiplier = rtl ? -1 : 1; + leftComponent.style.width = this.startWidth_ + + dirMultiplier * (e.clientX - this.startX_) + 'px'; + }, + + /** + * Handles the mouse up event which ends the dragging of the splitter. + * @param {!Event} e The mouse event. + * @private + */ + handleMouseUp_: function(e) { + this.endDrag(); + } + }; + + return { + Splitter: Splitter + } +}); diff --git a/chrome/browser/resources/shared/js/cr/ui/tree.js b/chrome/browser/resources/shared/js/cr/ui/tree.js new file mode 100644 index 0000000..9e18321 --- /dev/null +++ b/chrome/browser/resources/shared/js/cr/ui/tree.js @@ -0,0 +1,664 @@ +// 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. + +cr.define('cr.ui', function() { + // require cr.ui.define + // require cr.ui.limitInputWidth + + /** + * The number of pixels to indent per level. + * @type {number} + */ + const INDENT = 20; + + /** + * Returns the computed style for an element. + * @param {!Element} el The element to get the computed style for. + * @return {!CSSStyleDeclaration} The computed style. + */ + function getComputedStyle(el) { + return el.ownerDocument.defaultView.getComputedStyle(el); + } + + /** + * Helper function that finds the first ancestor tree item. + * @param {!Element} el The element to start searching from. + * @return {cr.ui.TreeItem} The found tree item or null if not found. + */ + function findTreeItem(el) { + while (el && !(el instanceof TreeItem)) { + el = el.parentNode; + } + return el; + } + + /** + * Creates a new tree element. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLElement} + */ + var Tree = cr.ui.define('tree'); + + Tree.prototype = { + __proto__: HTMLElement.prototype, + + /** + * Initializes the element. + */ + decorate: function() { + // Make list focusable + if (!this.hasAttribute('tabindex')) + this.tabIndex = 0; + + this.addEventListener('click', this.handleClick); + this.addEventListener('mousedown', this.handleMouseDown); + this.addEventListener('dblclick', this.handleDblClick); + this.addEventListener('keydown', this.handleKeyDown); + }, + + /** + * Returns the tree item that are children of this tree. + */ + get items() { + return this.children; + }, + + /** + * Adds a tree item to the tree. + * @param {!cr.ui.TreeItem} treeItem The item to add. + */ + add: function(treeItem) { + this.addAt(treeItem, 0xffffffff); + }, + + /** + * Adds a tree item at the given index. + * @param {!cr.ui.TreeItem} treeItem The item to add. + * @param {number} index The index where we want to add the item. + */ + addAt: function(treeItem, index) { + this.insertBefore(treeItem, this.children[index]); + treeItem.setDepth_(this.depth + 1); + }, + + /** + * Removes a tree item child. + * @param {!cr.ui.TreeItem} treeItem The tree item to remove. + */ + remove: function(treeItem) { + this.removeChild(treeItem); + }, + + /** + * The depth of the node. This is 0 for the tree itself. + * @type {number} + */ + get depth() { + return 0; + }, + + /** + * Handles click events on the tree and forwards the event to the relevant + * tree items as necesary. + * @param {Event} e The click event object. + */ + handleClick: function(e) { + var treeItem = findTreeItem(e.target); + if (treeItem) + treeItem.handleClick(e); + }, + + handleMouseDown: function(e) { + if (e.button == 2) // right + this.handleClick(e); + }, + + /** + * Handles double click events on the tree. + * @param {Event} e The dblclick event object. + */ + handleDblClick: function(e) { + var treeItem = findTreeItem(e.target); + if (treeItem) + treeItem.expanded = !treeItem.expanded; + }, + + /** + * Handles keydown events on the tree and updates selection and exanding + * of tree items. + * @param {Event} e The click event object. + */ + handleKeyDown: function(e) { + var itemToSelect; + if (e.ctrlKey) + return; + + var item = this.selectedItem; + + var rtl = getComputedStyle(item).direction == 'rtl'; + + switch (e.keyIdentifier) { + case 'Up': + itemToSelect = item ? getPrevious(item) : + this.items[this.items.length - 1]; + break; + case 'Down': + itemToSelect = item ? getNext(item) : + this.items[0]; + break; + case 'Left': + case 'Right': + // Don't let back/forward keyboard shortcuts be used. + if (!cr.isMac && e.altKey || cr.isMac && e.metaKey) + break; + + if (e.keyIdentifier == 'Left' && !rtl || + e.keyIdentifier == 'Right' && rtl) { + if (item.expanded) + item.expanded = false; + else + itemToSelect = findTreeItem(item.parentNode); + } else { + if (!item.expanded) + item.expanded = true; + else + itemToSelect = item.items[0]; + } + break; + case 'Home': + itemToSelect = this.items[0]; + break; + case 'End': + itemToSelect = this.items[this.items.length - 1]; + break; + } + + if (itemToSelect) { + itemToSelect.selected = true; + e.preventDefault(); + } + }, + + /** + * The selected tree item or null if none. + * @type {cr.ui.TreeItem} + */ + get selectedItem() { + return this.selectedItem_ || null; + }, + set selectedItem(item) { + var oldSelectedItem = this.selectedItem_; + if (oldSelectedItem != item) { + // Set the selectedItem_ before deselecting the old item since we only + // want one change when moving between items. + this.selectedItem_ = item; + + if (oldSelectedItem) + oldSelectedItem.selected = false; + + if (item) + item.selected = true; + + cr.dispatchSimpleEvent(this, 'change'); + } + }, + + /** + * @return {!ClientRect} The rect to use for the context menu. + */ + getRectForContextMenu: function() { + // TODO(arv): Add trait support so we can share more code between trees + // and lists. + if (this.selectedItem) + return this.selectedItem.rowElement.getBoundingClientRect(); + return this.getBoundingClientRect(); + } + }; + + /** + * This is used as a blueprint for new tree item elements. + * @type {!HTMLElement} + */ + var treeItemProto = (function() { + var treeItem = cr.doc.createElement('div'); + treeItem.className = 'tree-item'; + treeItem.innerHTML = '
' + + '' + + '' + + '
' + + '
'; + return treeItem; + })(); + + /** + * Creates a new tree item. + * @param {Object=} opt_propertyBag Optional properties. + * @constructor + * @extends {HTMLElement} + */ + var TreeItem = cr.ui.define(function() { + return treeItemProto.cloneNode(true); + }); + + TreeItem.prototype = { + __proto__: HTMLElement.prototype, + + /** + * Initializes the element. + */ + decorate: function() { + + }, + + /** + * The tree items children. + */ + get items() { + return this.lastElementChild.children; + }, + + /** + * The depth of the tree item. + * @type {number} + */ + depth_: 0, + get depth() { + return this.depth_; + }, + + /** + * Sets the depth. + * @param {number} depth The new depth. + * @private + */ + setDepth_: function(depth) { + if (depth != this.depth_) { + this.rowElement.style.WebkitPaddingStart = Math.max(0, depth - 1) * + INDENT + 'px'; + this.depth_ = depth; + var items = this.items; + for (var i = 0, item; item = items[i]; i++) { + item.setDepth_(depth + 1); + } + } + }, + + /** + * Adds a tree item as a child. + * @param {!cr.ui.TreeItem} child The child to add. + */ + add: function(child) { + this.addAt(child, 0xffffffff); + }, + + /** + * Adds a tree item as a child at a given index. + * @param {!cr.ui.TreeItem} child The child to add. + * @param {number} index The index where to add the child. + */ + addAt: function(child, index) { + this.lastElementChild.insertBefore(child, this.items[index]); + if (this.items.length == 1) + this.hasChildren_ = true; + child.setDepth_(this.depth + 1); + }, + + /** + * Removes a child. + * @param {!cr.ui.TreeItem} child The tree item child to remove. + */ + remove: function(child) { + // If we removed the selected item we should become selected. + var tree = this.tree; + var selectedItem = tree.selectedItem; + if (selectedItem && child.contains(selectedItem)) + this.selected = true; + + this.lastElementChild.removeChild(child); + if (this.items.length == 0) + this.hasChildren_ = false; + }, + + /** + * The parent tree item. + * @type {!cr.ui.Tree|cr.ui.TreeItem} + */ + get parentItem() { + var p = this.parentNode; + while (p && !(p instanceof TreeItem) && !(p instanceof Tree)) { + p = p.parentNode; + } + return p; + }, + + /** + * The tree that the tree item belongs to or null of no added to a tree. + * @type {cr.ui.Tree} + */ + get tree() { + var t = this.parentItem; + while (t && !(t instanceof Tree)) { + t = t.parentItem; + } + return t; + }, + + /** + * Whether the tree item is expanded or not. + * @type {boolean} + */ + get expanded() { + return this.hasAttribute('expanded'); + }, + set expanded(b) { + if (this.expanded == b) + return; + + var treeChildren = this.lastElementChild; + + if (b) { + if (this.mayHaveChildren_) { + this.setAttribute('expanded', ''); + treeChildren.setAttribute('expanded', ''); + cr.dispatchSimpleEvent(this, 'expand', true); + this.scrollIntoViewIfNeeded(false); + } + } else { + var tree = this.tree; + if (tree && !this.selected) { + var oldSelected = tree.selectedItem; + if (oldSelected && this.contains(oldSelected)) + this.selected = true; + } + this.removeAttribute('expanded'); + treeChildren.removeAttribute('expanded'); + cr.dispatchSimpleEvent(this, 'collapse', true); + } + }, + + /** + * Expands all parent items. + */ + reveal: function() { + var pi = this.parentItem; + while (pi && !(pi instanceof Tree)) { + pi.expanded = true; + pi = pi.parentItem; + } + }, + + /** + * The element representing the row that gets highlighted. + * @type {!HTMLElement} + */ + get rowElement() { + return this.firstElementChild; + }, + + /** + * The element containing the label text and the icon. + * @type {!HTMLElement} + */ + get labelElement() { + return this.firstElementChild.lastElementChild; + }, + + /** + * The label text. + * @type {string} + */ + get label() { + return this.labelElement.textContent; + }, + set label(s) { + this.labelElement.textContent = s; + }, + + /** + * The URL for the icon. + * @type {string} + */ + get icon() { + return getComputedStyle(this.labelElement).backgroundImage.slice(4, -1); + }, + set icon(icon) { + return this.labelElement.style.backgroundImage = url(icon); + }, + + /** + * Whether the tree item is selected or not. + * @type {boolean} + */ + get selected() { + return this.hasAttribute('selected'); + }, + set selected(b) { + if (this.selected == b) + return; + var rowItem = this.firstElementChild; + var tree = this.tree; + if (b) { + this.setAttribute('selected', ''); + rowItem.setAttribute('selected', ''); + this.labelElement.scrollIntoViewIfNeeded(false); + if (tree) + tree.selectedItem = this; + } else { + this.removeAttribute('selected'); + rowItem.removeAttribute('selected'); + if (tree && tree.selectedItem == this) + tree.selectedItem = null; + } + }, + + /** + * Whether the tree item has children. + * @type {boolean} + */ + get mayHaveChildren_() { + return this.hasAttribute('may-have-children'); + }, + set mayHaveChildren_(b) { + var rowItem = this.firstElementChild; + if (b) { + this.setAttribute('may-have-children', ''); + rowItem.setAttribute('may-have-children', ''); + } else { + this.removeAttribute('may-have-children'); + rowItem.removeAttribute('may-have-children'); + } + }, + + /** + * Whether the tree item has children. + * @type {boolean} + */ + get hasChildren() { + return !!this.items[0]; + }, + + /** + * Whether the tree item has children. + * @type {boolean} + * @private + */ + set hasChildren_(b) { + var rowItem = this.firstElementChild; + this.setAttribute('has-children', b); + rowItem.setAttribute('has-children', b); + if (b) + this.mayHaveChildren_ = true; + }, + + /** + * Called when the user clicks on a tree item. This is forwarded from the + * cr.ui.Tree. + * @param {Event} e The click event. + */ + handleClick: function(e) { + if (e.target.className == 'expand-icon') + this.expanded = !this.expanded; + else + this.selected = true; + }, + + /** + * Makes the tree item user editable. If the user renamed the item a + * bubbling {@code rename} event is fired. + * @type {boolean} + */ + set editing(editing) { + var oldEditing = this.editing; + if (editing == oldEditing) + return; + + var self = this; + var labelEl = this.labelElement; + var text = this.label; + var input; + + // Handles enter and escape which trigger reset and commit respectively. + function handleKeydown(e) { + // Make sure that the tree does not handle the key. + e.stopPropagation(); + + // Calling tree.focus blurs the input which will make the tree item + // non editable. + switch (e.keyIdentifier) { + case 'U+001B': // Esc + input.value = text; + // fall through + case 'Enter': + self.tree.focus(); + } + } + + function stopPropagation(e) { + e.stopPropagation(); + } + + if (editing) { + this.selected = true; + this.setAttribute('editing', ''); + this.draggable = false; + + // We create an input[type=text] and copy over the label value. When + // the input loses focus we set editing to false again. + input = this.ownerDocument.createElement('input'); + input.value = text; + if (labelEl.firstChild) + labelEl.replaceChild(input, labelEl.firstChild); + else + labelEl.appendChild(input); + + input.addEventListener('keydown', handleKeydown); + input.addEventListener('blur', cr.bind(function() { + this.editing = false; + }, this)); + + // Make sure that double clicks do not expand and collapse the tree + // item. + var eventsToStop = ['mousedown', 'mouseup', 'contextmenu', 'dblclick']; + eventsToStop.forEach(function(type) { + input.addEventListener(type, stopPropagation); + }); + + // Wait for the input element to recieve focus before sizing it. + var rowElement = this.rowElement; + function onFocus() { + input.removeEventListener('focus', onFocus); + // 20 = the padding and border of the tree-row + cr.ui.limitInputWidth(input, rowElement, 20); + } + input.addEventListener('focus', onFocus); + input.focus(); + input.select(); + + this.oldLabel_ = text; + } else { + this.removeAttribute('editing'); + this.draggable = true; + input = labelEl.firstChild; + var value = input.value; + if (/^\s*$/.test(value)) { + labelEl.textContent = this.oldLabel_; + } else { + labelEl.textContent = value; + if (value != this.oldLabel_) { + cr.dispatchSimpleEvent(this, 'rename', true); + } + } + delete this.oldLabel_; + } + }, + + get editing() { + return this.hasAttribute('editing'); + } + }; + + /** + * Helper function that returns the next visible tree item. + * @param {cr.ui.TreeItem} item The tree item. + * @retrun {cr.ui.TreeItem} The found item or null. + */ + function getNext(item) { + if (item.expanded) { + var firstChild = item.items[0]; + if (firstChild) { + return firstChild; + } + } + + return getNextHelper(item); + } + + /** + * Another helper function that returns the next visible tree item. + * @param {cr.ui.TreeItem} item The tree item. + * @retrun {cr.ui.TreeItem} The found item or null. + */ + function getNextHelper(item) { + if (!item) + return null; + + var nextSibling = item.nextElementSibling; + if (nextSibling) { + return nextSibling; + } + return getNextHelper(item.parentItem); + } + + /** + * Helper function that returns the previous visible tree item. + * @param {cr.ui.TreeItem} item The tree item. + * @retrun {cr.ui.TreeItem} The found item or null. + */ + function getPrevious(item) { + var previousSibling = item.previousElementSibling; + return previousSibling ? getLastHelper(previousSibling) : item.parentItem; + } + + /** + * Helper function that returns the last visible tree item in the subtree. + * @param {cr.ui.TreeItem} item The item to find the last visible item for. + * @return {cr.ui.TreeItem} The found item or null. + */ + function getLastHelper(item) { + if (!item) + return null; + if (item.expanded && item.hasChildren) { + var lastChild = item.items[item.items.length - 1]; + return getLastHelper(lastChild); + } + return item; + } + + // Export + return { + Tree: Tree, + TreeItem: TreeItem + }; +}); diff --git a/chrome/browser/resources/shared/js/cr_test.html b/chrome/browser/resources/shared/js/cr_test.html new file mode 100644 index 0000000..5d6c16e --- /dev/null +++ b/chrome/browser/resources/shared/js/cr_test.html @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + diff --git a/chrome/browser/resources/shared/js/i18ntemplate.js b/chrome/browser/resources/shared/js/i18ntemplate.js new file mode 100644 index 0000000..8166ddc --- /dev/null +++ b/chrome/browser/resources/shared/js/i18ntemplate.js @@ -0,0 +1,104 @@ +/** + * @fileoverview This is a simple template engine inspired by JsTemplates + * optimized for i18n. + * + * It currently supports two handlers: + * + * * i18n-content which sets the textContent of the element + * + * + * i18nTemplate.process(element, {'myContent': 'Content'}); + * + * * i18n-values is a list of attribute-value or property-value pairs. + * Properties are prefixed with a '.' and can contain nested properties. + * + * + * i18nTemplate.process(element, { + * 'myTitle': 'Title', + * 'fontSize': '13px' + * }); + */ + +var i18nTemplate = (function() { + /** + * This provides the handlers for the templating engine. The key is used as + * the attribute name and the value is the function that gets called for every + * single node that has this attribute. + * @type {Object} + */ + var handlers = { + /** + * This handler sets the textContent of the element. + */ + 'i18n-content': function(element, attributeValue, obj) { + element.textContent = obj[attributeValue]; + }, + + /** + * This is used to set HTML attributes and DOM properties,. The syntax is: + * attributename:key; + * .domProperty:key; + * .nested.dom.property:key + */ + 'i18n-values': function(element, attributeValue, obj) { + var parts = attributeValue.replace(/\s/g, '').split(/;/); + for (var j = 0; j < parts.length; j++) { + var a = parts[j].match(/^([^:]+):(.+)$/); + if (a) { + var propName = a[1]; + var propExpr = a[2]; + + // Ignore missing properties + if (propExpr in obj) { + var value = obj[propExpr]; + if (propName.charAt(0) == '.') { + var path = propName.slice(1).split('.'); + var object = element; + while (object && path.length > 1) { + object = object[path.shift()]; + } + if (object) { + object[path] = value; + // In case we set innerHTML (ignoring others) we need to + // recursively check the content + if (path == 'innerHTML') { + process(element, obj); + } + } + } else { + element.setAttribute(propName, value); + } + } else { + console.warn('i18n-values: Missing value for "' + propExpr + '"'); + } + } + } + } + }; + + var attributeNames = []; + for (var key in handlers) { + attributeNames.push(key); + } + var selector = '[' + attributeNames.join('],[') + ']'; + + /** + * Processes a DOM tree with the {@code obj} map. + */ + function process(node, obj) { + var elements = node.querySelectorAll(selector); + for (var element, i = 0; element = elements[i]; i++) { + for (var j = 0; j < attributeNames.length; j++) { + var name = attributeNames[j]; + var att = element.getAttribute(name); + if (att != null) { + handlers[name](element, att, obj); + } + } + } + } + + return { + process: process + }; +})(); diff --git a/chrome/browser/resources/shared/js/localstrings.js b/chrome/browser/resources/shared/js/localstrings.js new file mode 100644 index 0000000..86f888b --- /dev/null +++ b/chrome/browser/resources/shared/js/localstrings.js @@ -0,0 +1,55 @@ +// Copyright (c) 2009-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. + +// TODO(arv): Namespace and share code with DOMUI + +/** + * The local strings get injected into the page usig a varaible named + * {@code templateData}. This class provides a simpler interface to access those + * strings. + * @constructor + */ +function LocalStrings() { +} + +LocalStrings.prototype = { + + /** + * The template data object. + * @type {Object} + */ + templateData: null, + + /** + * Gets a localized string by its id. + * @param {string} s The id of the string we want. + * @return {string} The localized string. + */ + getString: function(id) { + return this.templateData[id] || ''; + }, + + /** + * Returns a formatted localized string where all %s contents are replaced + * by the second argument and where $1 to $9 are replaced by the second to + * tenths arguments. + * @param {string} id The ID of the string we want. + * @param {string} v The string to include in the formatted string. + * @param {...string} The extra values to include in the fomatted output. + * @return {string} The formatted string. + */ + getStringF: function(id, v, var_args) { + // The localized messages should contain $n but they also use %s from time + // to time so we support both until all the messages have been unified. + var s = this.getString(id); + var args = arguments; + return s.replace(/%s|\$[$1-9]/g, function(m) { + if (m == '%s') + return v; + if (m == '$$') + return '$'; + return args[m[1]]; + }); + } +}; diff --git a/chrome/browser/resources/shared/js/util.js b/chrome/browser/resources/shared/js/util.js new file mode 100644 index 0000000..3fef5d2 --- /dev/null +++ b/chrome/browser/resources/shared/js/util.js @@ -0,0 +1,57 @@ +// 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. + +/** + * The global object. + * @param {!Object} + */ +const global = this; + +/** + * Alias for document.getElementById. + * @param {string} id The ID of the element to find. + * @return {HTMLElement} The found element or null if not found. + */ +function $(id) { + return document.getElementById(id); +} + +/** + * Calls chrome.send with a callback and restores the original afterwards. + * @param {string} name The name of the message to send. + * @param {!Array} params The parameters to send. + * @param {string} callbackName The name of the function that the backend calls. + * @param {!Function} The function to call. + */ +function chromeSend(name, params, callbackName, callback) { + var old = global[callbackName]; + global[callbackName] = function() { + // restore + global[callbackName] = old; + + var args = Array.prototype.slice.call(arguments); + return callback.apply(global, args); + }; + chrome.send(name, params); +} + + +/** + * Generates a CSS url string. + * @param {string} s The URL to generate the CSS url for. + * @return {string} The CSS url string. + */ +function url(s) { + // http://www.w3.org/TR/css3-values/#uris + // Parentheses, commas, whitespace characters, single quotes (') and double + // quotes (") appearing in a URI must be escaped with a backslash + var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1'); + // WebKit has a bug when it comes to URLs that end with \ + // https://bugs.webkit.org/show_bug.cgi?id=28885 + if (/\\\\$/.test(s2)) { + // Add a space to work around the WebKit bug. + s2 += ' '; + } + return 'url("' + s2 + '")'; +} diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index f676374..332e351 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -3161,56 +3161,70 @@ # http://code.google.com/p/gyp/issues/detail?id=143. 'copies': [ { - 'destination': '<(PRODUCT_DIR)/resources/bookmark_manager', + 'destination': '<(PRODUCT_DIR)/resources/shared/css', 'files': [ - 'browser/resources/bookmark_manager/main.html', - 'browser/resources/bookmark_manager/manifest.json', + 'browser/resources/shared/css/list.css', + 'browser/resources/shared/css/menu.css', + 'browser/resources/shared/css/tree.css', + 'browser/resources/shared/css/tree.css.js', ] }, { - 'destination': '<(PRODUCT_DIR)/resources/bookmark_manager/css', + 'destination': '<(PRODUCT_DIR)/resources/shared/js', 'files': [ - 'browser/resources/bookmark_manager/css/bmm.css', - 'browser/resources/bookmark_manager/css/bmm.css.js', - 'browser/resources/bookmark_manager/css/list.css', - 'browser/resources/bookmark_manager/css/menu.css', - 'browser/resources/bookmark_manager/css/tree.css', - 'browser/resources/bookmark_manager/css/tree.css.js', + 'browser/resources/shared/js/cr_test.html', + 'browser/resources/shared/js/cr.js', + 'browser/resources/shared/js/i18ntemplate.js', + 'browser/resources/shared/js/localstrings.js', + 'browser/resources/shared/js/util.js', ] }, { - 'destination': '<(PRODUCT_DIR)/resources/bookmark_manager/js', + 'destination': '<(PRODUCT_DIR)/resources/shared/js/cr', 'files': [ - 'browser/resources/bookmark_manager/js/bmm.js', - 'browser/resources/bookmark_manager/js/cr.js', - 'browser/resources/bookmark_manager/js/i18ntemplate.js', - 'browser/resources/bookmark_manager/js/localstrings.js', - 'browser/resources/bookmark_manager/js/util.js', + 'browser/resources/shared/js/cr/event.js', + 'browser/resources/shared/js/cr/eventtarget_test.html', + 'browser/resources/shared/js/cr/eventtarget.js', + 'browser/resources/shared/js/cr/linkcontroller_test.html', + 'browser/resources/shared/js/cr/linkcontroller.js', + 'browser/resources/shared/js/cr/promise_test.html', + 'browser/resources/shared/js/cr/promise.js', + 'browser/resources/shared/js/cr/ui.js', + ] + }, + { + 'destination': '<(PRODUCT_DIR)/resources/shared/js/cr/ui', + 'files': [ + 'browser/resources/shared/js/cr/ui/command.js', + 'browser/resources/shared/js/cr/ui/contextmenuhandler.js', + 'browser/resources/shared/js/cr/ui/list.js', + 'browser/resources/shared/js/cr/ui/listitem.js', + 'browser/resources/shared/js/cr/ui/listselectionmodel.js', + 'browser/resources/shared/js/cr/ui/menu.js', + 'browser/resources/shared/js/cr/ui/menubutton.js', + 'browser/resources/shared/js/cr/ui/menuitem.js', + 'browser/resources/shared/js/cr/ui/splitter.js', + 'browser/resources/shared/js/cr/ui/tree.js', ] }, { - 'destination': '<(PRODUCT_DIR)/resources/bookmark_manager/js/cr', + 'destination': '<(PRODUCT_DIR)/resources/bookmark_manager', + 'files': [ + 'browser/resources/bookmark_manager/main.html', + 'browser/resources/bookmark_manager/manifest.json', + ] + }, + { + 'destination': '<(PRODUCT_DIR)/resources/bookmark_manager/css', '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', + 'browser/resources/bookmark_manager/css/bmm.css', + 'browser/resources/bookmark_manager/css/bmm.css.js', ] }, { - 'destination': '<(PRODUCT_DIR)/resources/bookmark_manager/js/cr/ui', + 'destination': '<(PRODUCT_DIR)/resources/bookmark_manager/js', 'files': [ - 'browser/resources/bookmark_manager/js/cr/ui/command.js', - 'browser/resources/bookmark_manager/js/cr/ui/contextmenuhandler.js', - 'browser/resources/bookmark_manager/js/cr/ui/list.js', - 'browser/resources/bookmark_manager/js/cr/ui/listitem.js', - 'browser/resources/bookmark_manager/js/cr/ui/listselectionmodel.js', - 'browser/resources/bookmark_manager/js/cr/ui/menu.js', - 'browser/resources/bookmark_manager/js/cr/ui/menubutton.js', - 'browser/resources/bookmark_manager/js/cr/ui/menuitem.js', - 'browser/resources/bookmark_manager/js/cr/ui/splitter.js', - 'browser/resources/bookmark_manager/js/cr/ui/tree.js', + 'browser/resources/bookmark_manager/js/bmm.js', ] }, { diff --git a/chrome/chrome_dll.gypi b/chrome/chrome_dll.gypi index 27e648c..dd77f75 100644 --- a/chrome/chrome_dll.gypi +++ b/chrome/chrome_dll.gypi @@ -399,6 +399,7 @@ 'files': [ '<(PRODUCT_DIR)/resources/inspector/', '<(PRODUCT_DIR)/resources/bookmark_manager/', + '<(PRODUCT_DIR)/resources/shared/', '<(PRODUCT_DIR)/resources/gmail_app/', '<(PRODUCT_DIR)/resources/calendar_app/', '<(PRODUCT_DIR)/resources/docs_app/', diff --git a/chrome/common/chrome_paths.cc b/chrome/common/chrome_paths.cc index 3f5e055..eccb65b 100644 --- a/chrome/common/chrome_paths.cc +++ b/chrome/common/chrome_paths.cc @@ -151,6 +151,16 @@ bool PathProvider(int key, FilePath* result) { cur = cur.Append(FILE_PATH_LITERAL("resources")); #endif break; + case chrome::DIR_SHARED_RESOURCES: + if (!PathService::Get(chrome::DIR_RESOURCES, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("shared")); + break; + case chrome::DIR_BOOKMARK_MANAGER: + if (!PathService::Get(chrome::DIR_RESOURCES, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("bookmark_manager")); + break; case chrome::DIR_INSPECTOR: if (!PathService::Get(chrome::DIR_RESOURCES, &cur)) return false; diff --git a/chrome/common/chrome_paths.h b/chrome/common/chrome_paths.h index 47dff30..c5e13d2 100644 --- a/chrome/common/chrome_paths.h +++ b/chrome/common/chrome_paths.h @@ -22,6 +22,9 @@ enum { DIR_USER_DESKTOP, // Directory that correspond to the desktop. DIR_RESOURCES, // Directory containing separate file resources // used by Chrome at runtime. + DIR_SHARED_RESOURCES, // Directory containing js and css files used + // by DOMUI and component extensions. + DIR_BOOKMARK_MANAGER, // Directory containing the bookmark manager. DIR_INSPECTOR, // Directory where web inspector is located. DIR_NET_INTERNALS, // Directory where net internals is located. DIR_APP_DICTIONARIES, // Directory where the global dictionaries are. diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index 8dc62d2..41d5257 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -1385,11 +1385,7 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_key, return false; } - // We support http:// and https:// as well as chrome://favicon/. - if (!(pattern.scheme() == chrome::kHttpScheme || - pattern.scheme() == chrome::kHttpsScheme || - (pattern.scheme() == chrome::kChromeUIScheme && - pattern.host() == chrome::kChromeUIFavIconHost))) { + if (!CanAccessURL(pattern)) { *error = ExtensionErrorUtils::FormatErrorMessage( errors::kInvalidPermissionScheme, IntToString(i)); return false; @@ -1585,6 +1581,24 @@ Extension::Icons Extension::GetIconPathAllowLargerSize( return EXTENSION_ICON_LARGE; } +// We support http:// and https:// as well as chrome://favicon//. +// chrome://resources/ is supported but only for component extensions. +bool Extension::CanAccessURL(const URLPattern pattern) const{ + if (pattern.scheme() == chrome::kHttpScheme || + pattern.scheme() == chrome::kHttpsScheme) { + return true; + } + if (pattern.scheme() == chrome::kChromeUIScheme && + pattern.host() == chrome::kChromeUIFavIconHost) { + return true; + } + if (location() == Extension::COMPONENT && + pattern.scheme() == chrome::kChromeUIScheme) { + return true; + } + return false; +} + bool Extension::HasHostPermission(const GURL& url) const { for (URLPatternList::const_iterator host = host_permissions_.begin(); host != host_permissions_.end(); ++host) { diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 2cf2f85..7a1fe93 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -260,6 +260,12 @@ class Extension { // through content scripts and the hosts accessible through XHR. const std::set GetEffectiveHostPermissions() const; + // Whether or not the extension is allowed permission for a URL pattern from + // the manifest. http, https, and chrome://favicon/ is allowed for all + // extensions, while component extensions are allowed access to + // chrome://resources. + bool CanAccessURL(const URLPattern pattern) const; + // Whether the extension has access to the given URL. bool HasHostPermission(const GURL& url) const; diff --git a/chrome/common/extensions/extension_manifests_unittest.cc b/chrome/common/extensions/extension_manifests_unittest.cc index 66f8991..36e0266 100644 --- a/chrome/common/extensions/extension_manifests_unittest.cc +++ b/chrome/common/extensions/extension_manifests_unittest.cc @@ -23,6 +23,12 @@ class ManifestTest : public testing::Test { protected: Extension* LoadExtension(const std::string& name, std::string* error) { + return LoadExtensionWithLocation(name, Extension::INTERNAL, error); + } + + Extension* LoadExtensionWithLocation(const std::string& name, + Extension::Location location, + std::string* error) { FilePath path; PathService::Get(chrome::DIR_TEST_DATA, &path); path = path.AppendASCII("extensions") @@ -37,6 +43,7 @@ class ManifestTest : public testing::Test { return NULL; scoped_ptr extension(new Extension(path.DirName())); + extension->set_location(location); if (enable_apps_) extension->set_apps_enabled(true); @@ -191,6 +198,18 @@ TEST_F(ManifestTest, ChromeURLPermissionInvalid) { errors::kInvalidPermissionScheme); } +TEST_F(ManifestTest, ChromeResourcesPermissionValidOnlyForComponents) { + LoadAndExpectError("permission_chrome_resources_url.json", + errors::kInvalidPermissionScheme); + std::string error; + scoped_ptr extension; + extension.reset(LoadExtensionWithLocation( + "permission_chrome_resources_url.json", + Extension::COMPONENT, + &error)); + EXPECT_EQ("", error); +} + TEST_F(ManifestTest, ChromeURLContentScriptInvalid) { LoadAndExpectError("content_script_chrome_url_invalid.json", errors::kInvalidMatch); diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc index 513ee61..8b3e45b 100644 --- a/chrome/common/url_constants.cc +++ b/chrome/common/url_constants.cc @@ -74,6 +74,7 @@ const char kChromeUIExtensionsHost[] = "extensions"; const char kChromeUIFavIconHost[] = "favicon"; const char kChromeUIHistoryHost[] = "history"; const char kChromeUIPluginsHost[] = "plugins"; +const char kChromeUIResourcesHost[] = "resources"; const char kChromeUIFileBrowseHost[] = "filebrowse"; const char kChromeUIMediaplayerHost[] = "mediaplayer"; const char kChromeUIInspectorHost[] = "inspector"; diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h index b223f46..741a372 100644 --- a/chrome/common/url_constants.h +++ b/chrome/common/url_constants.h @@ -72,6 +72,7 @@ extern const char kChromeUIExtensionsHost[]; extern const char kChromeUIFavIconHost[]; extern const char kChromeUIHistoryHost[]; extern const char kChromeUIPluginsHost[]; +extern const char kChromeUIResourcesHost[]; extern const char kChromeUIFileBrowseHost[]; extern const char kChromeUIMediaplayerHost[]; extern const char kChromeUIInspectorHost[]; diff --git a/chrome/test/data/extensions/manifest_tests/permission_chrome_resources_url.json b/chrome/test/data/extensions/manifest_tests/permission_chrome_resources_url.json new file mode 100644 index 0000000..fa778e4 --- /dev/null +++ b/chrome/test/data/extensions/manifest_tests/permission_chrome_resources_url.json @@ -0,0 +1,7 @@ +{ + "name": "test", + "version": "1", + "permissions": [ + "chrome://resources/" + ] +} -- cgit v1.1