summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbauerb@chromium.org <bauerb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-23 15:18:06 +0000
committerbauerb@chromium.org <bauerb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-23 15:18:06 +0000
commit13663571e9be13a79b86f15bf15346d12d7e5ebd (patch)
tree8fab15a7e8978dfa5dbf142b4c4ad4d29f51e3d4
parent50bce4993a664114addf41c99ff61dfca19fab45 (diff)
downloadchromium_src-13663571e9be13a79b86f15bf15346d12d7e5ebd.zip
chromium_src-13663571e9be13a79b86f15bf15346d12d7e5ebd.tar.gz
chromium_src-13663571e9be13a79b86f15bf15346d12d7e5ebd.tar.bz2
Add sample extension that allows setting plugin-specific content settings.
BUG=64155 TEST=none Review URL: http://codereview.chromium.org/8396001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@111352 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings.zipbin0 -> 211259 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json23
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny128.pngbin0 -> 46533 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny48.pngbin0 -> 9422 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/css/plugin_list.css85
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/css/rule_list.css33
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/button.css43
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/chrome_shared.css257
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/list.css89
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/select.css51
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/images/select.pngbin0 -> 147 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr.js381
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/event_target.js104
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui.js161
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/array_data_model.js363
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list.js971
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_item.js75
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_controller.js289
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_model.js275
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_single_selection_model.js221
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/util.js151
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/js/main.js16
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list.js220
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings.js188
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list.js392
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json16
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/options.html38
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/options/css/list.css124
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar.pngbin0 -> 193 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_h.pngbin0 -> 227 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_p.pngbin0 -> 125 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/deletable_item_list.js185
-rw-r--r--chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/inline_editable_list.js414
-rw-r--r--chrome/common/extensions/docs/experimental.savePage.html4
-rw-r--r--chrome/common/extensions/docs/samples.html90
-rw-r--r--chrome/common/extensions/docs/samples.json48
36 files changed, 5305 insertions, 2 deletions
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings.zip b/chrome/common/extensions/docs/examples/extensions/plugin_settings.zip
new file mode 100644
index 0000000..ce75a5c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings.zip
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json b/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json
new file mode 100644
index 0000000..95524bd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json
@@ -0,0 +1,23 @@
+{
+ "extName": {
+ "message": "Per-plugin content settings"
+ },
+ "extDescription": {
+ "message": "Customize your content setting for different plug-ins."
+ },
+ "patternColumnHeader": {
+ "message": "Hostname Pattern"
+ },
+ "settingColumnHeader": {
+ "message": "Behavior"
+ },
+ "allowRule": {
+ "message": "Allow"
+ },
+ "blockRule": {
+ "message": "Block"
+ },
+ "addNewPattern": {
+ "message": "Add a new hostname pattern"
+ }
+} \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny128.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny128.png
new file mode 100644
index 0000000..917371d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny48.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny48.png
new file mode 100644
index 0000000..30434e4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/plugin_list.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/plugin_list.css
new file mode 100644
index 0000000..225bd3c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/plugin_list.css
@@ -0,0 +1,85 @@
+/*
+Copyright (c) 2011 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.
+*/
+
+body {
+ font-family: Helvetica, sans-serif;
+ background-color: white;
+ color: black;
+ margin: 10px;
+}
+
+.plugin-list {
+ border: 1px solid #d9d9d9;
+ border-radius: 2px;
+ width: 517px;
+}
+
+.plugin-list > li {
+ padding: 3px;
+}
+
+.plugin-name {
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-weight: bold;
+}
+
+.num-rules:before {
+ content: ' ';
+}
+
+.plugin-show-details .num-rules {
+ display: none;
+}
+
+.plugin-description {
+ display: inline-block;
+ -webkit-padding-start: 7px;
+ width: auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 95%;
+}
+
+.plugin-details {
+ -webkit-transition: height .5s ease-in-out;
+ background: #f5f8f8;
+ border: 1px solid #b2b2b2;
+ border-radius: 5px;
+ padding: 5px;
+ height: 0;
+ width: 500px;
+ opacity: 0;
+}
+
+.plugin-measure-details .plugin-details {
+ -webkit-transition: none;
+ height: auto;
+ visibility: hidden;
+}
+
+li.plugin-show-details {
+ height: auto;
+}
+
+.plugin-show-details .plugin-description {
+ height: auto;
+}
+
+.plugin-show-details .plugin-details {
+ opacity: 1;
+ height: auto;
+}
+
+.column-headers {
+ -webkit-margin-start: 17px;
+ display: -webkit-box;
+}
+
+.column-headers > div {
+ font-weight: bold;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/rule_list.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/rule_list.css
new file mode 100644
index 0000000..b67157b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/rule_list.css
@@ -0,0 +1,33 @@
+/*
+Copyright (c) 2011 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.
+*/
+
+.rule-pattern {
+ -webkit-box-flex: 1;
+ -webkit-margin-end: 10px;
+ -webkit-margin-start: 14px;
+}
+
+.rule-behavior {
+ display: inline-block;
+ width: 120px;
+}
+
+select.rule-behavior {
+ vertical-align: middle;
+}
+
+.rule-list {
+ border: 1px solid #d9d9d9;
+ border-radius: 2px;
+}
+
+.pattern-column-header {
+ -webkit-box-flex: 1;
+}
+
+.setting-column-header {
+ width: 145px;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/button.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/button.css
new file mode 100644
index 0000000..5564f42
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/button.css
@@ -0,0 +1,43 @@
+button,
+input[type='button'],
+input[type='submit'] {
+ -webkit-border-radius: 2px;
+ -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
+ -webkit-user-select: none;
+ background: -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
+ border: 1px solid #aaa;
+ color: #444;
+ font-size: inherit;
+ margin-bottom: 0px;
+ min-width: 4em;
+ padding: 3px 12px 3px 12px;
+}
+
+button:hover,
+input[type='button']:hover,
+input[type='submit']:hover {
+ -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.2);
+ background: #ebebeb -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);
+ border-color: #999;
+ color: #222;
+}
+
+button:active,
+input[type='button']:active,
+input[type='submit']:active {
+ -webkit-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.2);
+ background: #ebebeb -webkit-linear-gradient(#f4f4f4, #efefef 40%, #dcdcdc);
+ color: #333;
+}
+
+button[disabled],
+input[type='button'][disabled],
+input[type='submit'][disabled],
+button[disabled]:hover,
+input[type='button'][disabled]:hover,
+input[type='submit'][disabled]:hover {
+ -webkit-box-shadow: none;
+ background: -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
+ border-color: #aaa;
+ color: #888;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/chrome_shared.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/chrome_shared.css
new file mode 100644
index 0000000..fa70aa2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/chrome_shared.css
@@ -0,0 +1,257 @@
+/* Styles common to WebUI pages that share the options pages style */
+body {
+ cursor: default;
+ font-size: 13px;
+}
+
+a:link {
+ color: rgb(63, 110, 194);
+}
+
+a:active {
+ color: rgb(37, 64, 113);
+}
+
+#navbar-content-title {
+ -webkit-padding-end: 24px;
+ -webkit-user-select: none;
+ color: #53637d;
+ cursor: pointer;
+ font-size: 200%;
+ font-weight: normal;
+ margin: 0;
+ padding-bottom: 14px;
+ padding-top: 13px;
+ text-align: end;
+ text-shadow: white 0 1px 2px;
+}
+
+#main-content {
+ display: -webkit-box;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+}
+
+#navbar {
+ margin: 0;
+}
+
+#navbar-container {
+ -webkit-border-end: 1px solid #c6c9ce;
+ background: -webkit-linear-gradient(rgba(234, 238, 243, 0.2), #eaeef3),
+ -webkit-linear-gradient(left, #eaeef3, #eaeef3 97%, #d3d7db);
+ position: fixed;
+ bottom: 0;
+ /* We set both left and right for the sake of RTL. */
+ left: 0;
+ right: 0;
+ top: 0;
+ width: 216px;
+ z-index: 2;
+}
+
+html[dir='rtl'] #navbar-container {
+ background: -webkit-linear-gradient(rgba(234, 238, 243, 0), #EAEEF3),
+ -webkit-linear-gradient(right, #EAEEF3, #EAEEF3 97%, #D3D7DB);
+}
+
+html.hide-menu #navbar-container {
+ display: none;
+}
+
+#navbar-container > ul {
+ -webkit-user-select: none;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+.navbar-item {
+ border-bottom: 1px solid transparent;
+ border-top: 1px solid transparent;
+ color: #426dc9;
+ cursor: pointer;
+ display: block;
+ font-size: 105%;
+ outline: none;
+ padding: 7px 0;
+ text-align: end;
+ text-shadow: white 0 1px 1px;
+ -webkit-padding-end: 24px;
+}
+
+.navbar-item:focus {
+ border-bottom: 1px solid #8faad9;
+ border-top: 1px solid #8faad9;
+}
+
+.navbar-item-selected {
+ -webkit-box-shadow: 0px 1px 0px #f7f7f7;
+ background: -webkit-linear-gradient(left, #bbcee9, #bbcee9 97%, #aabedc);
+ border-bottom: 1px solid #8faad9;
+ border-top: 1px solid #8faad9;
+ color: black;
+ text-shadow: #bbcee9 0 1px 1px;
+}
+
+#mainview {
+ -webkit-box-align: stretch;
+ -webkit-padding-start: 216px;
+ margin: 0;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ z-index: 1;
+}
+
+html.hide-menu #mainview {
+ -webkit-padding-start: 0;
+}
+
+#mainview-content {
+ min-height: 100%;
+ position: relative;
+}
+
+#page-container {
+ box-sizing: border-box;
+ max-width: 888px;
+ min-width: 600px;
+ padding: 0 24px;
+}
+
+div.checkbox,
+div.radio {
+ margin: 5px 0;
+ color: #444;
+}
+
+div.disabled {
+ color: #888;
+}
+
+/* TEXT */
+input[type='password'],
+input[type='text'],
+input[type='url'],
+input:not([type]) {
+ -webkit-border-radius: 2px;
+ border: 1px solid #aaa;
+ font-size: inherit;
+ padding: 3px;
+}
+
+/* CHECKBOX, RADIO */
+input[type=checkbox],
+input[type=radio] {
+ margin-left: 0;
+ margin-right: 0;
+ position: relative;
+ top: 1px;
+}
+
+/* Checkbox and radio buttons have different sizes on different platforms. The
+ * following rules have platform specific tweaks.
+ * TODO(arv): Test the vertical position on Linux and CrOS as well.
+ */
+
+label > input[type=checkbox],
+label > input[type=radio] {
+ opacity: 0.7;
+ margin-top: 1px;
+}
+
+html[os=mac] label > input[type=checkbox],
+html[os=mac] label > input[type=radio] {
+ margin-top: 2px;
+}
+
+html[os=chromeos] label > input[type=checkbox],
+html[os=chromeos] label > input[type=radio] {
+ top: 2px;
+}
+
+/* Checkbox and radio hover visuals.
+ * Their appearance when checked is set to be the same.
+ */
+label:hover > input[type=checkbox]:not([disabled]),
+label:hover > input[type=radio]:not([disabled]),
+label > input:not([disabled]):checked {
+ opacity: 1;
+}
+
+label:hover > input[type=checkbox]:not([disabled]) ~ span,
+label:hover > input[type=radio]:not([disabled]) ~ span,
+label > input:not([disabled]):checked ~ span {
+ color: #222;
+}
+
+/* This will 'disable' the label associated with any input whose next sibling is
+ * the span containing the label (usually a checkbox or radio).
+ */
+label > input[disabled] ~ span {
+ color: #888;
+}
+
+/* Elements that need to be LTR even in an RTL context, but should align
+ * right. (Namely, URLs, search engine names, etc.)
+ */
+html[dir='rtl'] .weakrtl {
+ direction: ltr;
+ text-align: right;
+}
+
+/* Input fields in search engine table need to be weak-rtl. Since those input
+ * fields are generated for all cr.ListItem elements (and we only want weakrtl
+ * on some), the class needs to be on the enclosing div.
+ */
+html[dir='rtl'] div.weakrtl input {
+ direction: ltr;
+ text-align: right;
+}
+
+html[dir='rtl'] .favicon-cell.weakrtl {
+ -webkit-padding-end: 22px;
+ -webkit-padding-start: 0;
+}
+
+/* weakrtl for selection drop downs needs to account for the fact that
+ * Webkit does not honor the text-align attribute for the select element.
+ * (See Webkit bug #40216)
+ */
+html[dir='rtl'] select.weakrtl {
+ direction: rtl;
+}
+
+html[dir='rtl'] select.weakrtl option {
+ direction: ltr;
+}
+
+/* WebKit does not honor alignment for text specified via placeholder attrib.
+ * This CSS is a workaround. Please remove once WebKit bug is fixed.
+ * https://bugs.webkit.org/show_bug.cgi?id=63367
+ */
+html[dir='rtl'] input.weakrtl::-webkit-input-placeholder,
+html[dir='rtl'] .weakrtl input::-webkit-input-placeholder {
+ direction: rtl;
+}
+
+.page h1 {
+ -webkit-padding-end: 24px;
+ -webkit-user-select: none;
+ border-bottom: 1px solid #eeeeee;
+ color: #53637d;
+ font-size: 200%;
+ font-weight: normal;
+ margin: 0;
+ padding-bottom: 4px;
+ padding-top: 13px;
+ text-shadow: white 0 1px 2px;
+}
+
+
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/list.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/list.css
new file mode 100644
index 0000000..75d2a92
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/list.css
@@ -0,0 +1,89 @@
+
+list,
+grid {
+ display: block;
+ outline: none;
+ overflow: auto;
+ position: relative; /* Make sure that item offsets are relative to the
+ list. */
+}
+
+list > *,
+grid > * {
+ -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 > * {
+ display: block;
+}
+
+grid > * {
+ display: inline-block;
+}
+
+list > [lead],
+grid > [lead] {
+ border-color: transparent;
+}
+
+list:focus > [lead],
+grid:focus > [lead] {
+ border-color: hsl(214, 91%, 65%);
+ z-index: 2;
+}
+
+list > [anchor],
+grid > [anchor] {
+
+}
+
+list:not([disabled]) > :hover,
+grid:not([disabled]) > :hover {
+ border-color: hsl(214, 91%, 85%);
+ z-index: 1;
+ background-color: hsl(214, 91%, 97%);
+}
+
+list > [selected],
+grid > [selected] {
+ border-color: hsl(0, 0%, 85%);
+ background-color: hsl(0,0%,90%);
+ z-index: 2;
+ background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.8),
+ rgba(255, 255, 255, 0));
+}
+
+list:focus > [selected],
+grid:focus > [selected] {
+ background-color: hsl(214,91%,89%);
+ border-color: hsl(214, 91%, 65%);
+}
+
+list:focus > [lead][selected],
+list > [selected]:hover,
+grid:focus > [lead][selected],
+grid > [selected]:hover {
+ background-color: hsl(214, 91%, 87%);
+ border-color: hsl(214, 91%, 65%);
+}
+
+list > .spacer,
+grid > .spacer {
+ border: 0;
+ box-sizing: border-box;
+ display: block;
+ overflow: hidden;
+ visibility: hidden;
+ margin: 0;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/select.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/select.css
new file mode 100644
index 0000000..ba951e5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/select.css
@@ -0,0 +1,51 @@
+/* Copyright (c) 2011 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.
+ *
+ * This is the generic select css used on various WebUI implementations.
+ */
+
+select {
+ -webkit-appearance: button;
+ -webkit-border-radius: 2px;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ -webkit-padding-end: 20px;
+ -webkit-padding-start: 2px;
+ -webkit-user-select: none;
+ background-image: url("../images/select.png"),
+ -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
+ background-position: center right;
+ background-repeat: no-repeat;
+ border: 1px solid #aaa;
+ color: #555;
+ font-size: inherit;
+ margin: 0;
+ overflow: hidden;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+html[dir='rtl'] select {
+ background-position: center left;
+}
+
+
+select:disabled {
+ color: graytext;
+}
+
+select:enabled:hover {
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ background-image: url("../images/select.png"),
+ -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);
+ color: #333;
+}
+
+select:enabled:active {
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
+ background-image: url("../images/select.png"),
+ -webkit-linear-gradient(#f4f4f4, #efefef 40%, #dcdcdc);
+ color: #444;
+} \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/images/select.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/images/select.png
new file mode 100644
index 0000000..c20d304
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/images/select.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr.js
new file mode 100644
index 0000000..2a370a1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr.js
@@ -0,0 +1,381 @@
+// Copyright (c) 2011 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);
+
+ /**
+ * Whether this is on the Windows platform or not.
+ * @type {boolean}
+ */
+ const isWindows = /Win/.test(navigator.platform);
+
+ /**
+ * Whether this is on chromeOS or not.
+ * @type {boolean}
+ */
+ const isChromeOS = /CrOS/.test(navigator.userAgent);
+
+ /**
+ * Whether this is on touchui build or not.
+ * @type {boolean}
+ */
+ const isTouch = /Touch/.test(navigator.userAgent);
+
+ /**
+ * Whether this is on vanilla Linux (not chromeOS).
+ * @type {boolean}
+ */
+ const isLinux = /Linux/.test(navigator.userAgent);
+
+ /**
+ * Whether this uses GTK or not.
+ * @type {boolean}
+ */
+ const isGTK = /GTK/.test(chrome.toolkit);
+
+ /**
+ * Whether this uses the views toolkit or not.
+ * @type {boolean}
+ */
+ const isViews = /views/.test(chrome.toolkit);
+
+ /**
+ * Sets the os and toolkit attributes in the <html> element so that platform
+ * specific css rules can be applied.
+ */
+ function enablePlatformSpecificCSSRules() {
+ if (isMac)
+ doc.documentElement.setAttribute('os', 'mac');
+ if (isWindows)
+ doc.documentElement.setAttribute('os', 'windows');
+ if (isChromeOS)
+ doc.documentElement.setAttribute('os', 'chromeos');
+ if (isLinux)
+ doc.documentElement.setAttribute('os', 'linux');
+ if (isGTK)
+ doc.documentElement.setAttribute('toolkit', 'gtk');
+ if (isViews)
+ doc.documentElement.setAttribute('toolkit', 'views');
+ }
+
+ /**
+ * 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;
+ };
+
+ // 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
+ };
+
+ /**
+ * 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) {
+ var e = new CrEvent(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
+ * @return {function():*} The getter for the property.
+ */
+ function getGetter(name, kind) {
+ switch (kind) {
+ case PropertyKind.JS:
+ var privateName = name + '_';
+ return function() {
+ return this[privateName];
+ };
+ case PropertyKind.ATTR:
+ return function() {
+ return this.getAttribute(name);
+ };
+ case PropertyKind.BOOL_ATTR:
+ 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.
+ * @param {function(*):void} opt_setHook A function to run after the property
+ * is set, but before the propertyChange event is fired.
+ * @return {function(*):void} The function to use as a setter.
+ */
+ function getSetter(name, kind, opt_setHook) {
+ switch (kind) {
+ case PropertyKind.JS:
+ var privateName = name + '_';
+ return function(value) {
+ var oldValue = this[privateName];
+ if (value !== oldValue) {
+ this[privateName] = value;
+ if (opt_setHook)
+ opt_setHook.call(this, value, oldValue);
+ dispatchPropertyChange(this, name, value, oldValue);
+ }
+ };
+
+ case PropertyKind.ATTR:
+ return function(value) {
+ var oldValue = this[name];
+ if (value !== oldValue) {
+ if (value == undefined)
+ this.removeAttribute(name);
+ else
+ this.setAttribute(name, value);
+ if (opt_setHook)
+ opt_setHook.call(this, value, oldValue);
+ 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);
+ if (opt_setHook)
+ opt_setHook.call(this, value, oldValue);
+ 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} obj The object to define the property for.
+ * @param {string} name The name of the property.
+ * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use.
+ * @param {function(*):void} opt_setHook A function to run after the
+ * property is set, but before the propertyChange event is fired.
+ */
+ function defineProperty(obj, name, opt_kind, opt_setHook) {
+ if (typeof obj == 'function')
+ obj = obj.prototype;
+
+ var kind = opt_kind || PropertyKind.JS;
+
+ if (!obj.__lookupGetter__(name)) {
+ obj.__defineGetter__(name, getGetter(name, kind));
+ }
+
+ if (!obj.__lookupSetter__(name)) {
+ obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
+ }
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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 propertyName in exports) {
+ // Maybe we should check the prototype chain here? The current usage
+ // pattern is always using an object literal so we only care about own
+ // properties.
+ var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
+ propertyName);
+ if (propertyDescriptor)
+ Object.defineProperty(obj, propertyName, propertyDescriptor);
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * Adds a {@code getInstance} static method that always return the same
+ * instance object.
+ * @param {!Function} ctor The constructor for the class to add the static
+ * method to.
+ */
+ function addSingletonGetter(ctor) {
+ ctor.getInstance = function() {
+ return ctor.instance_ || (ctor.instance_ = new ctor());
+ };
+ }
+
+ return {
+ addSingletonGetter: addSingletonGetter,
+ isChromeOS: isChromeOS,
+ isMac: isMac,
+ isWindows: isWindows,
+ isLinux: isLinux,
+ isViews: isViews,
+ isTouch: isTouch,
+ enablePlatformSpecificCSSRules: enablePlatformSpecificCSSRules,
+ define: define,
+ defineProperty: defineProperty,
+ PropertyKind: PropertyKind,
+ createUid: createUid,
+ getUid: getUid,
+ dispatchSimpleEvent: dispatchSimpleEvent,
+ dispatchPropertyChange: dispatchPropertyChange,
+
+ /**
+ * The document that we are currently using.
+ * @type {!Document}
+ */
+ get doc() {
+ return doc;
+ },
+ withDoc: withDoc,
+ Event: CrEvent
+ };
+})();
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/event_target.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/event_target.js
new file mode 100644
index 0000000..5bcb41d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/event_target.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/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui.js
new file mode 100644
index 0000000..ea286b2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui.js
@@ -0,0 +1,161 @@
+// 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:
+ * <pre>
+ * var List = cr.ui.define('list');
+ * List.prototype = {
+ * __proto__: HTMLUListElement.prototype,
+ * decorate: function() {
+ * ...
+ * },
+ * ...
+ * };
+ * </pre>
+ *
+ * @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);
+
+ var max = parentEl.clientWidth - startPos - inner - parentPadding;
+
+ 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/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/array_data_model.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/array_data_model.js
new file mode 100644
index 0000000..f12bd1d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/array_data_model.js
@@ -0,0 +1,363 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This is a data model representin
+ */
+
+cr.define('cr.ui', function() {
+ const EventTarget = cr.EventTarget;
+ const Event = cr.Event;
+
+ /**
+ * A data model that wraps a simple array and supports sorting by storing
+ * initial indexes of elements for each position in sorted array.
+ * @param {!Array} array The underlying array.
+ * @constructor
+ * @extends {EventTarget}
+ */
+ function ArrayDataModel(array) {
+ this.array_ = array;
+ this.indexes_ = [];
+ this.compareFunctions_ = {};
+
+ for (var i = 0; i < array.length; i++) {
+ this.indexes_.push(i);
+ }
+ }
+
+ ArrayDataModel.prototype = {
+ __proto__: EventTarget.prototype,
+
+ /**
+ * The length of the data model.
+ * @type {number}
+ */
+ get length() {
+ return this.array_.length;
+ },
+
+ /**
+ * Returns the item at the given index.
+ * This implementation returns the item at the given index in the sorted
+ * array.
+ * @param {number} index The index of the element to get.
+ * @return {*} The element at the given index.
+ */
+ item: function(index) {
+ if (index >= 0 && index < this.length)
+ return this.array_[this.indexes_[index]];
+ return undefined;
+ },
+
+ /**
+ * Returns compare function set for given field.
+ * @param {string} field The field to get compare function for.
+ * @return {function(*, *): number} Compare function set for given field.
+ */
+ compareFunction: function(field) {
+ return this.compareFunctions_[field];
+ },
+
+ /**
+ * Sets compare function for given field.
+ * @param {string} field The field to set compare function.
+ * @param {function(*, *): number} Compare function to set for given field.
+ */
+ setCompareFunction: function(field, compareFunction) {
+ if (!this.compareFunctions_) {
+ this.compareFunctions_ = {};
+ }
+ this.compareFunctions_[field] = compareFunction;
+ },
+
+ /**
+ * Returns current sort status.
+ * @return {!Object} Current sort status.
+ */
+ get sortStatus() {
+ if (this.sortStatus_) {
+ return this.createSortStatus(
+ this.sortStatus_.field, this.sortStatus_.direction);
+ } else {
+ return this.createSortStatus(null, null);
+ }
+ },
+
+ /**
+ * Returns the first matching item.
+ * @param {*} item The item to find.
+ * @param {number=} opt_fromIndex If provided, then the searching start at
+ * the {@code opt_fromIndex}.
+ * @return {number} The index of the first found element or -1 if not found.
+ */
+ indexOf: function(item, opt_fromIndex) {
+ return this.array_.indexOf(item, opt_fromIndex);
+ },
+
+ /**
+ * Returns an array of elements in a selected range.
+ * @param {number=} opt_from The starting index of the selected range.
+ * @param {number=} opt_to The ending index of selected range.
+ * @return {Array} An array of elements in the selected range.
+ */
+ slice: function(opt_from, opt_to) {
+ return this.array_.slice.apply(this.array_, arguments);
+ },
+
+ /**
+ * This removes and adds items to the model.
+ * This dispatches a splice event.
+ * This implementation runs sort after splice and creates permutation for
+ * the whole change.
+ * @param {number} index The index of the item to update.
+ * @param {number} deleteCount The number of items to remove.
+ * @param {...*} The items to add.
+ * @return {!Array} An array with the removed items.
+ */
+ splice: function(index, deleteCount, var_args) {
+ var addCount = arguments.length - 2;
+ var newIndexes = [];
+ var deletePermutation = [];
+ var deleted = 0;
+ for (var i = 0; i < this.indexes_.length; i++) {
+ var oldIndex = this.indexes_[i];
+ if (oldIndex < index) {
+ newIndexes.push(oldIndex);
+ deletePermutation.push(i - deleted);
+ } else if (oldIndex >= index + deleteCount) {
+ newIndexes.push(oldIndex - deleteCount + addCount);
+ deletePermutation.push(i - deleted);
+ } else {
+ deletePermutation.push(-1);
+ deleted++;
+ }
+ }
+ for (var i = 0; i < addCount; i++) {
+ newIndexes.push(index + i);
+ }
+ this.indexes_ = newIndexes;
+
+ var arr = this.array_;
+
+ // TODO(arv): Maybe unify splice and change events?
+ var spliceEvent = new Event('splice');
+ spliceEvent.index = index;
+ spliceEvent.removed = arr.slice(index, index + deleteCount);
+ spliceEvent.added = Array.prototype.slice.call(arguments, 2);
+
+ var rv = arr.splice.apply(arr, arguments);
+
+ var status = this.sortStatus;
+ // if sortStatus.field is null, this restores original order.
+ var sortPermutation = this.doSort_(this.sortStatus.field,
+ this.sortStatus.direction);
+ if (sortPermutation) {
+ var splicePermutation = deletePermutation.map(function(element) {
+ return element != -1 ? sortPermutation[element] : -1;
+ });
+ this.dispatchPermutedEvent_(splicePermutation);
+ } else {
+ this.dispatchPermutedEvent_(deletePermutation);
+ }
+
+ this.dispatchEvent(spliceEvent);
+
+ // If real sorting is needed, we should first call prepareSort (data may
+ // change), and then sort again.
+ // Still need to finish the sorting above (including events), so
+ // list will not go to inconsistent state.
+ if (status.field) {
+ setTimeout(this.sort.bind(this, status.field, status.direction), 0);
+ }
+ return rv;
+ },
+
+ /**
+ * Appends items to the end of the model.
+ *
+ * This dispatches a splice event.
+ *
+ * @param {...*} The items to append.
+ * @return {number} The new length of the model.
+ */
+ push: function(var_args) {
+ var args = Array.prototype.slice.call(arguments);
+ args.unshift(this.length, 0);
+ this.splice.apply(this, args);
+ return this.length;
+ },
+
+ /**
+ * Use this to update a given item in the array. This does not remove and
+ * reinsert a new item.
+ * This dispatches a change event.
+ * This runs sort after updating.
+ * @param {number} index The index of the item to update.
+ */
+ updateIndex: function(index) {
+ if (index < 0 || index >= this.length)
+ throw Error('Invalid index, ' + index);
+
+ // TODO(arv): Maybe unify splice and change events?
+ var e = new Event('change');
+ e.index = index;
+ this.dispatchEvent(e);
+
+ if (this.sortStatus.field) {
+ var status = this.sortStatus;
+ var sortPermutation = this.doSort_(this.sortStatus.field,
+ this.sortStatus.direction);
+ if (sortPermutation)
+ this.dispatchPermutedEvent_(sortPermutation);
+ // We should first call prepareSort (data may change), and then sort.
+ // Still need to finish the sorting above (including events), so
+ // list will not go to inconsistent state.
+ setTimeout(this.sort.bind(this, status.field, status.direction), 0);
+ }
+ },
+
+ /**
+ * Creates sort status with given field and direction.
+ * @param {string} field Sort field.
+ * @param {string} direction Sort direction.
+ * @return {!Object} Created sort status.
+ */
+ createSortStatus: function(field, direction) {
+ return {
+ field: field,
+ direction: direction
+ };
+ },
+
+ /**
+ * Called before a sort happens so that you may fetch additional data
+ * required for the sort.
+ *
+ * @param {string} field Sort field.
+ * @param {function()} callback The function to invoke when preparation
+ * is complete.
+ */
+ prepareSort: function(field, callback) {
+ callback();
+ },
+
+ /**
+ * Sorts data model according to given field and direction and dispathes
+ * sorted event.
+ * @param {string} field Sort field.
+ * @param {string} direction Sort direction.
+ */
+ sort: function(field, direction) {
+ var self = this;
+
+ this.prepareSort(field, function() {
+ var sortPermutation = self.doSort_(field, direction);
+ if (sortPermutation)
+ self.dispatchPermutedEvent_(sortPermutation);
+ self.dispatchSortEvent_();
+ });
+ },
+
+ /**
+ * Sorts data model according to given field and direction.
+ * @param {string} field Sort field.
+ * @param {string} direction Sort direction.
+ */
+ doSort_: function(field, direction) {
+ var compareFunction = this.sortFunction_(field, direction);
+ var positions = [];
+ for (var i = 0; i < this.length; i++) {
+ positions[this.indexes_[i]] = i;
+ }
+ this.indexes_.sort(compareFunction);
+ this.sortStatus_ = this.createSortStatus(field, direction);
+ var sortPermutation = [];
+ var changed = false;
+ for (var i = 0; i < this.length; i++) {
+ if (positions[this.indexes_[i]] != i)
+ changed = true;
+ sortPermutation[positions[this.indexes_[i]]] = i;
+ }
+ if (changed)
+ return sortPermutation;
+ return null;
+ },
+
+ dispatchSortEvent_: function() {
+ var e = new Event('sorted');
+ this.dispatchEvent(e);
+ },
+
+ dispatchPermutedEvent_: function(permutation) {
+ var e = new Event('permuted');
+ e.permutation = permutation;
+ e.newLength = this.length;
+ this.dispatchEvent(e);
+ },
+
+ /**
+ * Creates compare function for the field.
+ * Returns the function set as sortFunction for given field
+ * or default compare function
+ * @param {string} field Sort field.
+ * @param {function(*, *): number} Compare function.
+ */
+ createCompareFunction_: function(field) {
+ var compareFunction =
+ this.compareFunctions_ ? this.compareFunctions_[field] : null;
+ var defaultValuesCompareFunction = this.defaultValuesCompareFunction;
+ if (compareFunction) {
+ return compareFunction;
+ } else {
+ return function(a, b) {
+ return defaultValuesCompareFunction.call(null, a[field], b[field]);
+ }
+ }
+ return compareFunction;
+ },
+
+ /**
+ * Creates compare function for given field and direction.
+ * @param {string} field Sort field.
+ * @param {string} direction Sort direction.
+ * @param {function(*, *): number} Compare function.
+ */
+ sortFunction_: function(field, direction) {
+ var compareFunction = null;
+ if (field !== null)
+ compareFunction = this.createCompareFunction_(field);
+ var dirMultiplier = direction == 'desc' ? -1 : 1;
+
+ return function(index1, index2) {
+ var item1 = this.array_[index1];
+ var item2 = this.array_[index2];
+
+ var compareResult = 0;
+ if (typeof(compareFunction) === 'function')
+ compareResult = compareFunction.call(null, item1, item2);
+ if (compareResult != 0)
+ return dirMultiplier * compareResult;
+ return dirMultiplier * this.defaultValuesCompareFunction(index1,
+ index2);
+ }.bind(this);
+ },
+
+ /**
+ * Default compare function.
+ */
+ defaultValuesCompareFunction: function(a, b) {
+ // We could insert i18n comparisons here.
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ return 0;
+ }
+ };
+
+ return {
+ ArrayDataModel: ArrayDataModel
+ };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list.js
new file mode 100644
index 0000000..291d5ff
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list.js
@@ -0,0 +1,971 @@
+// Copyright (c) 2011 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: array_data_model.js
+// require: list_selection_model.js
+// require: list_selection_controller.js
+// require: list_item.js
+
+/**
+ * @fileoverview This implements a list control.
+ */
+
+cr.define('cr.ui', function() {
+ const ListSelectionModel = cr.ui.ListSelectionModel;
+ const ListSelectionController = cr.ui.ListSelectionController;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /**
+ * 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 an item (dataModel.item(0)) and measures its height.
+ * @param {!List} list The list to create the item for.
+ * @param {ListItem=} opt_item The list item to use to do the measuring. If
+ * this is not provided an item will be created based on the first value
+ * in the model.
+ * @return {{height: number, marginVertical: number, width: number,
+ * marginHorizontal: number}} The height and width of the item, taking
+ * margins into account, and the height and width of the margins
+ * themselves.
+ */
+ function measureItem(list, opt_item) {
+ var dataModel = list.dataModel;
+ if (!dataModel || !dataModel.length)
+ return 0;
+ var item = opt_item || list.createItem(dataModel.item(0));
+ if (!opt_item)
+ list.appendChild(item);
+
+ var rect = item.getBoundingClientRect();
+ var cs = getComputedStyle(item);
+ var mt = parseFloat(cs.marginTop);
+ var mb = parseFloat(cs.marginBottom);
+ var ml = parseFloat(cs.marginLeft);
+ var mr = parseFloat(cs.marginRight);
+ var h = rect.height;
+ var w = rect.width;
+ var mh = 0;
+ var mv = 0;
+
+ // Handle margin collapsing.
+ if (mt < 0 && mb < 0) {
+ mv = Math.min(mt, mb);
+ } else if (mt >= 0 && mb >= 0) {
+ mv = Math.max(mt, mb);
+ } else {
+ mv = mt + mb;
+ }
+ h += mv;
+
+ if (ml < 0 && mr < 0) {
+ mh = Math.min(ml, mr);
+ } else if (ml >= 0 && mr >= 0) {
+ mh = Math.max(ml, mr);
+ } else {
+ mh = ml + mr;
+ }
+ w += mh;
+
+ if (!opt_item)
+ list.removeChild(item);
+ return {
+ height: Math.max(0, h), marginVertical: mv,
+ width: Math.max(0, w), marginHorizontal: mh};
+ }
+
+ function getComputedStyle(el) {
+ return el.ownerDocument.defaultView.getComputedStyle(el);
+ }
+
+ /**
+ * 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,
+
+ /**
+ * Measured size of list items. This is lazily calculated the first time it
+ * is needed. Note that lead item is allowed to have a different height, to
+ * accommodate lists where a single item at a time can be expanded to show
+ * more detail.
+ * @type {{height: number, marginVertical: number, width: number,
+ * marginHorizontal: number}}
+ * @private
+ */
+ measured_: undefined,
+
+ /**
+ * The height of the lead item, which is allowed to have a different height
+ * than other list items to accommodate lists where a single item at a time
+ * can be expanded to show more detail. It is explicitly set by client code
+ * when the height of the lead item is changed with {@code set
+ * leadItemHeight}, and presumed equal to {@code itemHeight_} otherwise.
+ * @type {number}
+ * @private
+ */
+ leadItemHeight_: 0,
+
+ /**
+ * Whether or not the list is autoexpanding. If true, the list resizes
+ * its height to accomadate all children.
+ * @type {boolean}
+ * @private
+ */
+ autoExpands_: false,
+
+ /**
+ * Function used to create grid items.
+ * @type {function(): !ListItem}
+ * @private
+ */
+ itemConstructor_: cr.ui.ListItem,
+
+ /**
+ * Function used to create grid items.
+ * @type {function(): !ListItem}
+ */
+ get itemConstructor() {
+ return this.itemConstructor_;
+ },
+ set itemConstructor(func) {
+ if (func != this.itemConstructor_) {
+ this.itemConstructor_ = func;
+ this.cachedItems_ = {};
+ this.redraw();
+ }
+ },
+
+ dataModel_: null,
+
+ /**
+ * The data model driving the list.
+ * @type {ArrayDataModel}
+ */
+ set dataModel(dataModel) {
+ if (this.dataModel_ != dataModel) {
+ if (!this.boundHandleDataModelPermuted_) {
+ this.boundHandleDataModelPermuted_ =
+ this.handleDataModelPermuted_.bind(this);
+ this.boundHandleDataModelChange_ =
+ this.handleDataModelChange_.bind(this);
+ }
+
+ if (this.dataModel_) {
+ this.dataModel_.removeEventListener(
+ 'permuted',
+ this.boundHandleDataModelPermuted_);
+ this.dataModel_.removeEventListener('change',
+ this.boundHandleDataModelChange_);
+ }
+
+ this.dataModel_ = dataModel;
+
+ this.cachedItems_ = {};
+ this.selectionModel.clear();
+ if (dataModel)
+ this.selectionModel.adjustLength(dataModel.length);
+
+ if (this.dataModel_) {
+ this.dataModel_.addEventListener(
+ 'permuted',
+ this.boundHandleDataModelPermuted_);
+ this.dataModel_.addEventListener('change',
+ this.boundHandleDataModelChange_);
+ }
+
+ this.redraw();
+ }
+ },
+
+ get dataModel() {
+ return this.dataModel_;
+ },
+
+ /**
+ * 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_ = this.handleOnChange_.bind(this);
+ this.boundHandleLeadChange_ = this.handleLeadChange_.bind(this);
+ }
+
+ if (oldSm) {
+ oldSm.removeEventListener('change', this.boundHandleOnChange_);
+ oldSm.removeEventListener('leadIndexChange',
+ this.boundHandleLeadChange_);
+ }
+
+ this.selectionModel_ = sm;
+ this.selectionController_ = this.createSelectionController(sm);
+
+ if (sm) {
+ sm.addEventListener('change', this.boundHandleOnChange_);
+ sm.addEventListener('leadIndexChange', this.boundHandleLeadChange_);
+ }
+ },
+
+ /**
+ * Whether or not the list auto-expands.
+ * @type {boolean}
+ */
+ get autoExpands() {
+ return this.autoExpands_;
+ },
+ set autoExpands(autoExpands) {
+ if (this.autoExpands_ == autoExpands)
+ return;
+ this.autoExpands_ = autoExpands;
+ this.redraw();
+ },
+
+ /**
+ * Convenience alias for selectionModel.selectedItem
+ * @type {cr.ui.ListItem}
+ */
+ get selectedItem() {
+ var dataModel = this.dataModel;
+ if (dataModel) {
+ var index = this.selectionModel.selectedIndex;
+ if (index != -1)
+ return dataModel.item(index);
+ }
+ return null;
+ },
+ set selectedItem(selectedItem) {
+ var dataModel = this.dataModel;
+ if (dataModel) {
+ var index = this.dataModel.indexOf(selectedItem);
+ this.selectionModel.selectedIndex = index;
+ }
+ },
+
+ /**
+ * The height of the lead item.
+ * If set to 0, resets to the same height as other items.
+ * @type {number}
+ */
+ get leadItemHeight() {
+ return this.leadItemHeight_ || this.getItemHeight_();
+ },
+ set leadItemHeight(height) {
+ if (height) {
+ var size = this.getItemSize_();
+ this.leadItemHeight_ = Math.max(0, height + size.marginVertical);
+ } else {
+ this.leadItemHeight_ = 0;
+ }
+ },
+
+ /**
+ * Convenience alias for selectionModel.selectedItems
+ * @type {!Array<cr.ui.ListItem>}
+ */
+ get selectedItems() {
+ var indexes = this.selectionModel.selectedIndexes;
+ var dataModel = this.dataModel;
+ if (dataModel) {
+ return indexes.map(function(i) {
+ return dataModel.item(i);
+ });
+ }
+ return [];
+ },
+
+ /**
+ * The HTML elements representing the items. This is just all the list item
+ * children but subclasses may override this to filter out certain elements.
+ * @type {HTMLCollection}
+ */
+ get items() {
+ return Array.prototype.filter.call(this.children, function(child) {
+ return !child.classList.contains('spacer');
+ });
+ },
+
+ batchCount_: 0,
+
+ /**
+ * When making a lot of updates to the list, the code could be wrapped in
+ * the startBatchUpdates and finishBatchUpdates to increase performance. Be
+ * sure that the code will not return without calling endBatchUpdates or the
+ * list will not be correctly updated.
+ */
+ startBatchUpdates: function() {
+ this.batchCount_++;
+ },
+
+ /**
+ * See startBatchUpdates.
+ */
+ endBatchUpdates: function() {
+ this.batchCount_--;
+ if (this.batchCount_ == 0)
+ this.redraw();
+ },
+
+ /**
+ * Initializes the element.
+ */
+ decorate: function() {
+ // Add fillers.
+ this.beforeFiller_ = this.ownerDocument.createElement('div');
+ this.afterFiller_ = this.ownerDocument.createElement('div');
+ this.beforeFiller_.className = 'spacer';
+ this.afterFiller_.className = 'spacer';
+ this.appendChild(this.beforeFiller_);
+ this.appendChild(this.afterFiller_);
+
+ var length = this.dataModel ? this.dataModel.length : 0;
+ this.selectionModel = new ListSelectionModel(length);
+
+ this.addEventListener('dblclick', this.handleDoubleClick_);
+ this.addEventListener('mousedown', this.handleMouseDownUp_);
+ this.addEventListener('mouseup', this.handleMouseDownUp_);
+ this.addEventListener('keydown', this.handleKeyDown);
+ this.addEventListener('focus', this.handleElementFocus_, true);
+ this.addEventListener('blur', this.handleElementBlur_, true);
+ this.addEventListener('scroll', this.redraw.bind(this));
+ this.setAttribute('role', 'listbox');
+
+ // Make list focusable
+ if (!this.hasAttribute('tabindex'))
+ this.tabIndex = 0;
+ },
+
+ /**
+ * @return {number} The height of an item, measuring it if necessary.
+ * @private
+ */
+ getItemHeight_: function() {
+ return this.getItemSize_().height;
+ },
+
+ /**
+ * @return {number} The width of an item, measuring it if necessary.
+ * @private
+ */
+ getItemWidth_: function() {
+ return this.getItemSize_().width;
+ },
+
+ /**
+ * @return {{height: number, width: number}} The height and width
+ * of an item, measuring it if necessary.
+ * @private
+ */
+ getItemSize_: function() {
+ if (!this.measured_ || !this.measured_.height) {
+ this.measured_ = measureItem(this);
+ }
+ return this.measured_;
+ },
+
+ /**
+ * Callback for the double click event.
+ * @param {Event} e The mouse event object.
+ * @private
+ */
+ handleDoubleClick_: function(e) {
+ if (this.disabled)
+ return;
+
+ var target = this.getListItemAncestor(e.target);
+ if (target)
+ this.activateItemAtIndex(this.getIndexOfListItem(target));
+ },
+
+ /**
+ * Callback for mousedown and mouseup events.
+ * @param {Event} e The mouse event object.
+ * @private
+ */
+ handleMouseDownUp_: function(e) {
+ if (this.disabled)
+ return;
+
+ 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;
+
+ target = this.getListItemAncestor(target);
+
+ var index = target ? this.getIndexOfListItem(target) : -1;
+ this.selectionController_.handleMouseDownUp(e, index);
+ },
+
+ /**
+ * Called when an element in the list is focused. Marks the list as having
+ * a focused element, and dispatches an event if it didn't have focus.
+ * @param {Event} e The focus event.
+ * @private
+ */
+ handleElementFocus_: function(e) {
+ if (!this.hasElementFocus) {
+ this.hasElementFocus = true;
+ // Force styles based on hasElementFocus to take effect.
+ this.forceRepaint_();
+ }
+ },
+
+ /**
+ * Called when an element in the list is blurred. If focus moves outside
+ * the list, marks the list as no longer having focus and dispatches an
+ * event.
+ * @param {Event} e The blur event.
+ * @private
+ */
+ handleElementBlur_: function(e) {
+ // When the blur event happens we do not know who is getting focus so we
+ // delay this a bit until we know if the new focus node is outside the
+ // list.
+ var list = this;
+ var doc = e.target.ownerDocument;
+ window.setTimeout(function() {
+ var activeElement = doc.activeElement;
+ if (!list.contains(activeElement)) {
+ list.hasElementFocus = false;
+ // Force styles based on hasElementFocus to take effect.
+ list.forceRepaint_();
+ }
+ });
+ },
+
+ /**
+ * Forces a repaint of the list. Changing custom attributes, even if there
+ * are style rules depending on them, doesn't cause a repaint
+ * (<https://bugs.webkit.org/show_bug.cgi?id=12519>), so this can be called
+ * to force the list to repaint.
+ * @private
+ */
+ forceRepaint_: function(e) {
+ var dummyElement = document.createElement('div');
+ this.appendChild(dummyElement);
+ this.removeChild(dummyElement);
+ },
+
+ /**
+ * Returns the list item element containing the given element, or null if
+ * it doesn't belong to any list item element.
+ * @param {HTMLElement} element The element.
+ * @return {ListItem} The list item containing |element|, or null.
+ */
+ getListItemAncestor: function(element) {
+ var container = element;
+ while (container && container.parentNode != this) {
+ container = container.parentNode;
+ }
+ return container;
+ },
+
+ /**
+ * Handle a keydown event.
+ * @param {Event} e The keydown event.
+ * @return {boolean} Whether the key event was handled.
+ */
+ handleKeyDown: function(e) {
+ if (this.disabled)
+ return;
+
+ return this.selectionController_.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.getListItemByIndex(change.index);
+ if (listItem)
+ 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) {
+ var element;
+ if (pe.oldValue != -1) {
+ if ((element = this.getListItemByIndex(pe.oldValue)))
+ element.lead = false;
+ }
+
+ if (pe.newValue != -1) {
+ if ((element = this.getListItemByIndex(pe.newValue)))
+ element.lead = true;
+ this.scrollIndexIntoView(pe.newValue);
+ // If the lead item has a different height than other items, then we
+ // may run into a problem that requires a second attempt to scroll
+ // it into view. The first scroll attempt will trigger a redraw,
+ // which will clear out the list and repopulate it with new items.
+ // During the redraw, the list may shrink temporarily, which if the
+ // lead item is the last item, will move the scrollTop up since it
+ // cannot extend beyond the end of the list. (Sadly, being scrolled to
+ // the bottom of the list is not "sticky.") So, we set a timeout to
+ // rescroll the list after this all gets sorted out. This is perhaps
+ // not the most elegant solution, but no others seem obvious.
+ var self = this;
+ window.setTimeout(function() {
+ self.scrollIndexIntoView(pe.newValue);
+ });
+ }
+ },
+
+ /**
+ * This handles data model 'permuted' event.
+ * this event is dispatched as a part of sort or splice.
+ * We need to
+ * - adjust the cache.
+ * - adjust selection.
+ * - redraw.
+ * - scroll the list to show selection.
+ * It is important that the cache adjustment happens before selection model
+ * adjustments.
+ * @param {Event} e The 'permuted' event.
+ */
+ handleDataModelPermuted_: function(e) {
+ var newCachedItems = {};
+ for (var index in this.cachedItems_) {
+ if (e.permutation[index] != -1)
+ newCachedItems[e.permutation[index]] = this.cachedItems_[index];
+ else
+ delete this.cachedItems_[index];
+ }
+ this.cachedItems_ = newCachedItems;
+
+ this.startBatchUpdates();
+
+ var sm = this.selectionModel;
+ sm.adjustLength(e.newLength);
+ sm.adjustToReordering(e.permutation);
+
+ this.endBatchUpdates();
+
+ if (sm.leadIndex != -1)
+ this.scrollIndexIntoView(sm.leadIndex);
+ },
+
+ handleDataModelChange_: function(e) {
+ if (e.index >= this.firstIndex_ && e.index < this.lastIndex_) {
+ if (this.cachedItems_[e.index])
+ delete this.cachedItems_[e.index];
+ this.redraw();
+ }
+ },
+
+ /**
+ * @param {number} index The index of the item.
+ * @return {number} The top position of the item inside the list, not taking
+ * into account lead item. May vary in the case of multiple columns.
+ */
+ getItemTop: function(index) {
+ return index * this.getItemHeight_();
+ },
+
+ /**
+ * @param {number} index The index of the item.
+ * @return {number} The row of the item. May vary in the case
+ * of multiple columns.
+ */
+ getItemRow: function(index) {
+ return index;
+ },
+
+ /**
+ * @param {number} row The row.
+ * @return {number} The index of the first item in the row.
+ */
+ getFirstItemInRow: function(row) {
+ return row;
+ },
+
+ /**
+ * Ensures that a given index is inside the viewport.
+ * @param {number} index The index of the item to scroll into view.
+ * @return {boolean} Whether any scrolling was needed.
+ */
+ scrollIndexIntoView: function(index) {
+ var dataModel = this.dataModel;
+ if (!dataModel || index < 0 || index >= dataModel.length)
+ return false;
+
+ var itemHeight = this.getItemHeight_();
+ var scrollTop = this.scrollTop;
+ var top = this.getItemTop(index);
+ var leadIndex = this.selectionModel.leadIndex;
+
+ // Adjust for the lead item if it is above the given index.
+ if (leadIndex > -1 && leadIndex < index)
+ top += this.leadItemHeight - itemHeight;
+ else if (leadIndex == index)
+ itemHeight = this.leadItemHeight;
+
+ if (top < scrollTop) {
+ this.scrollTop = top;
+ return true;
+ } else {
+ var clientHeight = this.clientHeight;
+ var cs = getComputedStyle(this);
+ var paddingY = parseInt(cs.paddingTop, 10) +
+ parseInt(cs.paddingBottom, 10);
+
+ if (top + itemHeight > scrollTop + clientHeight - paddingY) {
+ this.scrollTop = top + itemHeight - clientHeight + paddingY;
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * @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.
+ var index = this.selectionModel.selectedIndex;
+ var el = this.getListItemByIndex(index);
+ if (el)
+ return el.getBoundingClientRect();
+ return this.getBoundingClientRect();
+ },
+
+ /**
+ * Takes a value from the data model and finds the associated list item.
+ * @param {*} value The value in the data model that we want to get the list
+ * item for.
+ * @return {ListItem} The first found list item or null if not found.
+ */
+ getListItem: function(value) {
+ var dataModel = this.dataModel;
+ if (dataModel) {
+ var index = dataModel.indexOf(value);
+ return this.getListItemByIndex(index);
+ }
+ return null;
+ },
+
+ /**
+ * Find the list item element at the given index.
+ * @param {number} index The index of the list item to get.
+ * @return {ListItem} The found list item or null if not found.
+ */
+ getListItemByIndex: function(index) {
+ return this.cachedItems_[index] || null;
+ },
+
+ /**
+ * Find the index of the given list item element.
+ * @param {ListItem} item The list item to get the index of.
+ * @return {number} The index of the list item, or -1 if not found.
+ */
+ getIndexOfListItem: function(item) {
+ var index = item.listIndex;
+ if (this.cachedItems_[index] == item) {
+ return index;
+ }
+ return -1;
+ },
+
+ /**
+ * Creates a new list item.
+ * @param {*} value The value to use for the item.
+ * @return {!ListItem} The newly created list item.
+ */
+ createItem: function(value) {
+ var item = new this.itemConstructor_(value);
+ item.label = value;
+ if (typeof item.decorate == 'function')
+ item.decorate();
+ return item;
+ },
+
+ /**
+ * Creates the selection controller to use internally.
+ * @param {cr.ui.ListSelectionModel} sm The underlying selection model.
+ * @return {!cr.ui.ListSelectionController} The newly created selection
+ * controller.
+ */
+ createSelectionController: function(sm) {
+ return new ListSelectionController(sm);
+ },
+
+ /**
+ * Return the heights (in pixels) of the top of the given item index within
+ * the list, and the height of the given item itself, accounting for the
+ * possibility that the lead item may be a different height.
+ * @param {number} index The index to find the top height of.
+ * @return {{top: number, height: number}} The heights for the given index.
+ * @private
+ */
+ getHeightsForIndex_: function(index) {
+ var itemHeight = this.getItemHeight_();
+ var top = this.getItemTop(index);
+ if (this.selectionModel.leadIndex > -1 &&
+ this.selectionModel.leadIndex < index) {
+ top += this.leadItemHeight - itemHeight;
+ } else if (this.selectionModel.leadIndex == index) {
+ itemHeight = this.leadItemHeight;
+ }
+ return {top: top, height: itemHeight};
+ },
+
+ /**
+ * Find the index of the list item containing the given y offset (measured
+ * in pixels from the top) within the list. In the case of multiple columns,
+ * returns the first index in the row.
+ * @param {number} offset The y offset in pixels to get the index of.
+ * @return {number} The index of the list item.
+ * @private
+ */
+ getIndexForListOffset_: function(offset) {
+ var itemHeight = this.getItemHeight_();
+ var leadIndex = this.selectionModel.leadIndex;
+ var leadItemHeight = this.leadItemHeight;
+ if (leadIndex < 0 || leadItemHeight == itemHeight) {
+ // Simple case: no lead item or lead item height is not different.
+ return this.getFirstItemInRow(Math.floor(offset / itemHeight));
+ }
+ var leadTop = this.getItemTop(leadIndex);
+ // If the given offset is above the lead item, it's also simple.
+ if (offset < leadTop)
+ return this.getFirstItemInRow(Math.floor(offset / itemHeight));
+ // If the lead item contains the given offset, we just return its index.
+ if (offset < leadTop + leadItemHeight)
+ return this.getFirstItemInRow(this.getItemRow(leadIndex));
+ // The given offset must be below the lead item. Adjust and recalculate.
+ offset -= leadItemHeight - itemHeight;
+ return this.getFirstItemInRow(Math.floor(offset / itemHeight));
+ },
+
+ /**
+ * Return the number of items that occupy the range of heights between the
+ * top of the start item and the end offset.
+ * @param {number} startIndex The index of the first visible item.
+ * @param {number} endOffset The y offset in pixels of the end of the list.
+ * @return {number} The number of list items visible.
+ * @private
+ */
+ countItemsInRange_: function(startIndex, endOffset) {
+ var endIndex = this.getIndexForListOffset_(endOffset);
+ return endIndex - startIndex + 1;
+ },
+
+ /**
+ * Calculates the number of items fitting in viewport given the index of
+ * first item and heights.
+ * @param {number} itemHeight The height of the item.
+ * @param {number} firstIndex Index of the first item in viewport.
+ * @param {number} scrollTop The scroll top position.
+ * @return {number} The number of items in view port.
+ */
+ getItemsInViewPort: function(itemHeight, firstIndex, scrollTop) {
+ // This is a bit tricky. We take the minimum of the available items to
+ // show and the number we want to show, so as not to go off the end of the
+ // list. For the number we want to show, we take the maximum of the number
+ // that would fit without a differently-sized lead item, and with one. We
+ // do this so that if the size of the lead item changes without a scroll
+ // event to trigger redrawing the list, we won't end up with empty space.
+ var clientHeight = this.clientHeight;
+ return this.autoExpands_ ? this.dataModel.length : Math.min(
+ this.dataModel.length - firstIndex,
+ Math.max(
+ Math.ceil(clientHeight / itemHeight) + 1,
+ this.countItemsInRange_(firstIndex, scrollTop + clientHeight)));
+ },
+
+ /**
+ * Adds items to the list and {@code newCachedItems}.
+ * @param {number} firstIndex The index of first item, inclusively.
+ * @param {number} lastIndex The index of last item, exclusively.
+ * @param {Object.<string, ListItem>} cachedItems Old items cache.
+ * @param {Object.<string, ListItem>} newCachedItems New items cache.
+ */
+ addItems: function(firstIndex, lastIndex, cachedItems, newCachedItems) {
+ var listItem;
+ var dataModel = this.dataModel;
+
+ window.l = this;
+ for (var y = firstIndex; y < lastIndex; y++) {
+ var dataItem = dataModel.item(y);
+ listItem = cachedItems[y] || this.createItem(dataItem);
+ listItem.listIndex = y;
+ this.appendChild(listItem);
+ newCachedItems[y] = listItem;
+ }
+ },
+
+ /**
+ * Returns the height of after filler in the list.
+ * @param {number} lastIndex The index of item past the last in viewport.
+ * @param {number} itemHeight The height of the item.
+ * @return {number} The height of after filler.
+ */
+ getAfterFillerHeight: function(lastIndex, itemHeight) {
+ return (this.dataModel.length - lastIndex) * itemHeight;
+ },
+
+ /**
+ * Redraws the viewport.
+ */
+ redraw: function() {
+ if (this.batchCount_ != 0)
+ return;
+
+ var dataModel = this.dataModel;
+ if (!dataModel) {
+ this.textContent = '';
+ return;
+ }
+
+ var scrollTop = this.scrollTop;
+ var clientHeight = this.clientHeight;
+
+ var itemHeight = this.getItemHeight_();
+
+ // We cache the list items since creating the DOM nodes is the most
+ // expensive part of redrawing.
+ var cachedItems = this.cachedItems_ || {};
+ var newCachedItems = {};
+
+ var desiredScrollHeight = this.getHeightsForIndex_(dataModel.length).top;
+
+ var autoExpands = this.autoExpands_;
+ var firstIndex = autoExpands ? 0 : this.getIndexForListOffset_(scrollTop);
+ var itemsInViewPort = this.getItemsInViewPort(itemHeight, firstIndex,
+ scrollTop);
+ var lastIndex = firstIndex + itemsInViewPort;
+
+ this.textContent = '';
+
+ this.beforeFiller_.style.height =
+ this.getHeightsForIndex_(firstIndex).top + 'px';
+ this.appendChild(this.beforeFiller_);
+
+ var sm = this.selectionModel;
+ var leadIndex = sm.leadIndex;
+
+ this.addItems(firstIndex, lastIndex, cachedItems, newCachedItems);
+
+ var afterFillerHeight = this.getAfterFillerHeight(lastIndex, itemHeight);
+ if (leadIndex >= lastIndex)
+ afterFillerHeight += this.leadItemHeight - itemHeight;
+ this.afterFiller_.style.height = afterFillerHeight + 'px';
+ this.appendChild(this.afterFiller_);
+
+ // We don't set the lead or selected properties until after adding all
+ // items, in case they force relayout in response to these events.
+ var listItem = null;
+ if (newCachedItems[leadIndex])
+ newCachedItems[leadIndex].lead = true;
+ for (var y = firstIndex; y < lastIndex; y++) {
+ if (sm.getIndexSelected(y))
+ newCachedItems[y].selected = true;
+ else if (y != leadIndex)
+ listItem = newCachedItems[y];
+ }
+
+ this.firstIndex_ = firstIndex;
+ this.lastIndex_ = lastIndex;
+
+ this.cachedItems_ = newCachedItems;
+
+ // Measure again in case the item height has changed due to a page zoom.
+ //
+ // The measure above is only done the first time but this measure is done
+ // after every redraw. It is done in a timeout so it will not trigger
+ // a reflow (which made the redraw speed 3 times slower on my system).
+ // By using a timeout the measuring will happen later when there is no
+ // need for a reflow.
+ if (listItem) {
+ var list = this;
+ window.setTimeout(function() {
+ if (listItem.parentNode == list) {
+ list.measured_ = measureItem(list, listItem);
+ }
+ });
+ }
+ },
+
+ /**
+ * Invalidates list by removing cached items.
+ */
+ invalidate: function() {
+ this.cachedItems_ = {};
+ },
+
+ /**
+ * Redraws a single item.
+ * @param {number} index The row index to redraw.
+ */
+ redrawItem: function(index) {
+ if (index >= this.firstIndex_ && index < this.lastIndex_) {
+ delete this.cachedItems_[index];
+ this.redraw();
+ }
+ },
+
+ /**
+ * Called when a list item is activated, currently only by a double click
+ * event.
+ * @param {number} index The index of the activated item.
+ */
+ activateItemAtIndex: function(index) {
+ },
+ };
+
+ cr.defineProperty(List, 'disabled', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Whether the list or one of its descendents has focus. This is necessary
+ * because list items can contain controls that can be focused, and for some
+ * purposes (e.g., styling), the list can still be conceptually focused at
+ * that point even though it doesn't actually have the page focus.
+ */
+ cr.defineProperty(List, 'hasElementFocus', cr.PropertyKind.BOOL_ATTR);
+
+ return {
+ List: List
+ }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_item.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_item.js
new file mode 100644
index 0000000..68431bc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_item.js
@@ -0,0 +1,75 @@
+// Copyright (c) 2011 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;
+ },
+
+ /**
+ * This item's index in the containing list.
+ * @type {number}
+ */
+ listIndex_: -1,
+
+ /**
+ * Called when an element is decorated as a list item.
+ */
+ decorate: function() {
+ this.setAttribute('role', 'listitem');
+ },
+
+ /**
+ * Called when the selection state of this element changes.
+ */
+ selectionChanged: function() {
+ },
+ };
+
+ /**
+ * Whether the item is selected. Setting this does not update the underlying
+ * selection model. This is only used for display purpose.
+ * @type {boolean}
+ */
+ cr.defineProperty(ListItem, 'selected', cr.PropertyKind.BOOL_ATTR,
+ function() {
+ this.selectionChanged();
+ });
+
+ /**
+ * 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}
+ */
+ cr.defineProperty(ListItem, 'lead', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * This item's index in the containing list.
+ * @type {number}
+ */
+ cr.defineProperty(ListItem, 'listIndex');
+
+ return {
+ ListItem: ListItem
+ };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_controller.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_controller.js
new file mode 100644
index 0000000..35101d1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_controller.js
@@ -0,0 +1,289 @@
+// 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 selection controller 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 getIndexBefore},
+ * {@code getIndexAfter}, {@code getIndexAbove} as well as
+ * {@code getIndexBelow}.
+ *
+ * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
+ * interact with.
+ *
+ * @constructor
+ * @extends {!cr.EventTarget}
+ */
+ function ListSelectionController(selectionModel) {
+ this.selectionModel_ = selectionModel;
+ }
+
+ ListSelectionController.prototype = {
+
+ /**
+ * The selection model we are interacting with.
+ * @type {cr.ui.ListSelectionModel}
+ */
+ get selectionModel() {
+ return this.selectionModel_;
+ },
+
+ /**
+ * Returns the index below (y axis) the given element.
+ * @param {number} index The index to get the index below.
+ * @return {number} The index below or -1 if not found.
+ */
+ getIndexBelow: function(index) {
+ if (index == this.getLastIndex())
+ return -1;
+ return index + 1;
+ },
+
+ /**
+ * Returns the index above (y axis) the given element.
+ * @param {number} index The index to get the index above.
+ * @return {number} The index below or -1 if not found.
+ */
+ getIndexAbove: function(index) {
+ return index - 1;
+ },
+
+ /**
+ * Returns the index before (x axis) the given element. This returns -1
+ * by default but override this for icon view and horizontal selection
+ * models.
+ *
+ * @param {number} index The index to get the index before.
+ * @return {number} The index before or -1 if not found.
+ */
+ getIndexBefore: function(index) {
+ return -1;
+ },
+
+ /**
+ * Returns the index after (x axis) the given element. This returns -1
+ * by default but override this for icon view and horizontal selection
+ * models.
+ *
+ * @param {number} index The index to get the index after.
+ * @return {number} The index after or -1 if not found.
+ */
+ getIndexAfter: function(index) {
+ return -1;
+ },
+
+ /**
+ * Returns the next list index. This is the next logical and should not
+ * depend on any kind of layout of the list.
+ * @param {number} index The index to get the next index for.
+ * @return {number} The next index or -1 if not found.
+ */
+ getNextIndex: function(index) {
+ if (index == this.getLastIndex())
+ return -1;
+ return index + 1;
+ },
+
+ /**
+ * Returns the prevous list index. This is the previous logical and should
+ * not depend on any kind of layout of the list.
+ * @param {number} index The index to get the previous index for.
+ * @return {number} The previous index or -1 if not found.
+ */
+ getPreviousIndex: function(index) {
+ return index - 1;
+ },
+
+ /**
+ * @return {number} The first index.
+ */
+ getFirstIndex: function() {
+ return 0;
+ },
+
+ /**
+ * @return {number} The last index.
+ */
+ getLastIndex: function() {
+ return this.selectionModel.length - 1;
+ },
+
+ /**
+ * Called by the view when the user does a mousedown or mouseup on the list.
+ * @param {!Event} e The browser mousedown event.
+ * @param {number} index The index that was under the mouse pointer, -1 if
+ * none.
+ */
+ handleMouseDownUp: function(e, index) {
+ var sm = this.selectionModel;
+ var anchorIndex = sm.anchorIndex;
+ var isDown = e.type == 'mousedown';
+
+ sm.beginChange();
+
+ if (index == -1) {
+ // 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) {
+ sm.leadIndex = sm.anchorIndex = -1;
+ if (sm.multiple)
+ sm.unselectAll();
+ } else if (!isDown && !e.shiftKey && !e.ctrlKey)
+ // Keep anchor and lead indexes. Note that this is intentionally
+ // different than on the Mac.
+ if (sm.multiple)
+ sm.unselectAll();
+ } else {
+ if (sm.multiple && (cr.isMac ? e.metaKey :
+ (e.ctrlKey && !e.shiftKey))) {
+ // Selection is handled at mouseUp on windows/linux, mouseDown on mac.
+ if (cr.isMac? isDown : !isDown) {
+ // Toggle the current one and make it anchor index.
+ sm.setIndexSelected(index, !sm.getIndexSelected(index));
+ sm.leadIndex = index;
+ sm.anchorIndex = index;
+ }
+ } else if (e.shiftKey && anchorIndex != -1 && anchorIndex != index) {
+ // Shift is done in mousedown.
+ if (isDown) {
+ sm.unselectAll();
+ sm.leadIndex = index;
+ if (sm.multiple)
+ sm.selectRange(anchorIndex, index);
+ else
+ sm.setIndexSelected(index, true);
+ }
+ } else {
+ // Right click for a context menu needs to not clear the selection.
+ var isRightClick = e.button == 2;
+
+ // If the index is selected this is handled in mouseup.
+ var indexSelected = sm.getIndexSelected(index);
+ if ((indexSelected && !isDown || !indexSelected && isDown) &&
+ !(indexSelected && isRightClick)) {
+ sm.unselectAll();
+ sm.setIndexSelected(index, true);
+ sm.leadIndex = index;
+ sm.anchorIndex = index;
+ }
+ }
+ }
+
+ sm.endChange();
+ },
+
+ /**
+ * Called by the view when it receives a keydown event.
+ * @param {Event} e The keydown event.
+ */
+ handleKeyDown: function(e) {
+ const SPACE_KEY_CODE = 32;
+ var tagName = e.target.tagName;
+ // If focus is in an input field of some kind, only handle navigation keys
+ // that aren't likely to conflict with input interaction (e.g., text
+ // editing, or changing the value of a checkbox or select).
+ if (tagName == 'INPUT') {
+ var inputType = e.target.type;
+ // Just protect space (for toggling) for checkbox and radio.
+ if (inputType == 'checkbox' || inputType == 'radio') {
+ if (e.keyCode == SPACE_KEY_CODE)
+ return;
+ // Protect all but the most basic navigation commands in anything else.
+ } else if (e.keyIdentifier != 'Up' && e.keyIdentifier != 'Down') {
+ return;
+ }
+ }
+ // Similarly, don't interfere with select element handling.
+ if (tagName == 'SELECT')
+ return;
+
+ var sm = this.selectionModel;
+ var newIndex = -1;
+ var leadIndex = sm.leadIndex;
+ var prevent = true;
+
+ // Ctrl/Meta+A
+ if (sm.multiple && e.keyCode == 65 &&
+ (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) {
+ sm.selectAll();
+ e.preventDefault();
+ return;
+ }
+
+ // Space
+ if (e.keyCode == SPACE_KEY_CODE) {
+ if (leadIndex != -1) {
+ var selected = sm.getIndexSelected(leadIndex);
+ if (e.ctrlKey || !selected) {
+ sm.setIndexSelected(leadIndex, !selected || !sm.multiple);
+ return;
+ }
+ }
+ }
+
+ switch (e.keyIdentifier) {
+ case 'Home':
+ newIndex = this.getFirstIndex();
+ break;
+ case 'End':
+ newIndex = this.getLastIndex();
+ break;
+ case 'Up':
+ newIndex = leadIndex == -1 ?
+ this.getLastIndex() : this.getIndexAbove(leadIndex);
+ break;
+ case 'Down':
+ newIndex = leadIndex == -1 ?
+ this.getFirstIndex() : this.getIndexBelow(leadIndex);
+ break;
+ case 'Left':
+ newIndex = leadIndex == -1 ?
+ this.getLastIndex() : this.getIndexBefore(leadIndex);
+ break;
+ case 'Right':
+ newIndex = leadIndex == -1 ?
+ this.getFirstIndex() : this.getIndexAfter(leadIndex);
+ break;
+ default:
+ prevent = false;
+ }
+
+ if (newIndex != -1) {
+ sm.beginChange();
+
+ sm.leadIndex = newIndex;
+ if (e.shiftKey) {
+ var anchorIndex = sm.anchorIndex;
+ if (sm.multiple)
+ sm.unselectAll();
+ if (anchorIndex == -1) {
+ sm.setIndexSelected(newIndex, true);
+ sm.anchorIndex = newIndex;
+ } else {
+ sm.selectRange(anchorIndex, newIndex);
+ }
+ } else if (e.ctrlKey && !cr.isMac) {
+ // Setting the lead index is done above.
+ // Mac does not allow you to change the lead.
+ } else {
+ if (sm.multiple)
+ sm.unselectAll();
+ sm.setIndexSelected(newIndex, true);
+ sm.anchorIndex = newIndex;
+ }
+
+ sm.endChange();
+
+ if (prevent)
+ e.preventDefault();
+ }
+ }
+ };
+
+ return {
+ ListSelectionController: ListSelectionController
+ };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_model.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_model.js
new file mode 100644
index 0000000..8c16d57
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_model.js
@@ -0,0 +1,275 @@
+// Copyright (c) 2011 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.
+ *
+ * @param {number=} opt_length The number items in the selection.
+ *
+ * @constructor
+ * @extends {!cr.EventTarget}
+ */
+ function ListSelectionModel(opt_length) {
+ this.length_ = opt_length || 0;
+ // Even though selectedIndexes_ is really a map we use an array here to get
+ // iteration in the order of the indexes.
+ this.selectedIndexes_ = [];
+ }
+
+ ListSelectionModel.prototype = {
+ __proto__: EventTarget.prototype,
+
+ /**
+ * The number of items in the model.
+ * @type {number}
+ */
+ get length() {
+ return this.length_;
+ },
+
+ /**
+ * @type {!Array} The selected indexes.
+ */
+ get selectedIndexes() {
+ return Object.keys(this.selectedIndexes_).map(Number);
+ },
+ set selectedIndexes(selectedIndexes) {
+ this.beginChange();
+ this.unselectAll();
+ for (var i = 0; i < selectedIndexes.length; i++) {
+ this.setIndexSelected(selectedIndexes[i], true);
+ }
+ if (selectedIndexes.length) {
+ this.leadIndex = this.anchorIndex = selectedIndexes[0];
+ } else {
+ this.leadIndex = this.anchorIndex = -1;
+ }
+ this.endChange();
+ },
+
+ /**
+ * Convenience getter which returns the first selected index.
+ * @type {number}
+ */
+ get selectedIndex() {
+ for (var i in this.selectedIndexes_) {
+ return Number(i);
+ }
+ return -1;
+ },
+ set selectedIndex(selectedIndex) {
+ this.beginChange();
+ this.unselectAll();
+ if (selectedIndex != -1) {
+ this.selectedIndexes = [selectedIndex];
+ } else {
+ this.leadIndex = this.anchorIndex = -1;
+ }
+ this.endChange();
+ },
+
+ /**
+ * Selects a range of indexes, starting with {@code start} and ends with
+ * {@code end}.
+ * @param {number} start The first index to select.
+ * @param {number} end The last index to select.
+ */
+ selectRange: function(start, end) {
+ // Swap if starts comes after end.
+ if (start > end) {
+ var tmp = start;
+ start = end;
+ end = tmp;
+ }
+
+ this.beginChange();
+
+ for (var index = start; index != end; index++) {
+ this.setIndexSelected(index, true);
+ }
+ this.setIndexSelected(end, true);
+
+ this.endChange();
+ },
+
+ /**
+ * Selects all indexes.
+ */
+ selectAll: function() {
+ this.selectRange(0, this.length - 1);
+ },
+
+ /**
+ * Clears the selection
+ */
+ clear: function() {
+ this.beginChange();
+ this.length_ = 0;
+ this.anchorIndex = this.leadIndex = -1;
+ this.unselectAll();
+ this.endChange();
+ },
+
+ /**
+ * Unselects all selected items.
+ */
+ unselectAll: function() {
+ this.beginChange();
+ for (var i in this.selectedIndexes_) {
+ this.setIndexSelected(i, false);
+ }
+ this.endChange();
+ },
+
+ /**
+ * Sets the selected state for an index.
+ * @param {number} index The index to set the selected state for.
+ * @param {boolean} b Whether to select the index or not.
+ */
+ setIndexSelected: function(index, b) {
+ var oldSelected = index in this.selectedIndexes_;
+ if (oldSelected == b)
+ return;
+
+ if (b)
+ this.selectedIndexes_[index] = true;
+ else
+ delete this.selectedIndexes_[index];
+
+ this.beginChange();
+
+ // Changing back?
+ if (index in this.changedIndexes_ && this.changedIndexes_[index] == !b) {
+ delete this.changedIndexes_[index];
+ } else {
+ this.changedIndexes_[index] = b;
+ }
+
+ // End change dispatches an event which in turn may update the view.
+ this.endChange();
+ },
+
+ /**
+ * Whether a given index is selected or not.
+ * @param {number} index The index to check.
+ * @return {boolean} Whether an index is selected.
+ */
+ getIndexSelected: function(index) {
+ return index in this.selectedIndexes_;
+ },
+
+ /**
+ * This is used to begin batching changes. Call {@code endChange} when you
+ * are done making changes.
+ */
+ beginChange: function() {
+ if (!this.changeCount_) {
+ this.changeCount_ = 0;
+ this.changedIndexes_ = {};
+ }
+ this.changeCount_++;
+ },
+
+ /**
+ * Call this after changes are done and it will dispatch a change event if
+ * any changes were actually done.
+ */
+ endChange: function() {
+ this.changeCount_--;
+ if (!this.changeCount_) {
+ var indexes = Object.keys(this.changedIndexes_);
+ if (indexes.length) {
+ var e = new Event('change');
+ e.changes = indexes.map(function(index) {
+ return {
+ index: index,
+ selected: this.changedIndexes_[index]
+ };
+ }, this);
+ this.dispatchEvent(e);
+ }
+ this.changedIndexes_ = {};
+ }
+ },
+
+ leadIndex_: -1,
+
+ /**
+ * The leadIndex is used with multiple selection and it is the index that
+ * the user is moving using the arrow keys.
+ * @type {number}
+ */
+ get leadIndex() {
+ return this.leadIndex_;
+ },
+ set leadIndex(leadIndex) {
+ var li = Math.max(-1, Math.min(this.length_ - 1, leadIndex));
+ if (li != this.leadIndex_) {
+ var oldLeadIndex = this.leadIndex_;
+ this.leadIndex_ = li;
+ cr.dispatchPropertyChange(this, 'leadIndex', li, oldLeadIndex);
+ }
+ },
+
+ anchorIndex_: -1,
+
+ /**
+ * The anchorIndex is used with multiple selection.
+ * @type {number}
+ */
+ get anchorIndex() {
+ return this.anchorIndex_;
+ },
+ set anchorIndex(anchorIndex) {
+ var ai = Math.max(-1, Math.min(this.length_ - 1, anchorIndex));
+ if (ai != this.anchorIndex_) {
+ var oldAnchorIndex = this.anchorIndex_;
+ this.anchorIndex_ = ai;
+ cr.dispatchPropertyChange(this, 'anchorIndex', ai, oldAnchorIndex);
+ }
+ },
+
+ /**
+ * Whether the selection model supports multiple selected items.
+ * @type {boolean}
+ */
+ get multiple() {
+ return true;
+ },
+
+ /**
+ * Adjusts the selection after reordering of items in the table.
+ * @param {!Array.<number>} permutation The reordering permutation.
+ */
+ adjustToReordering: function(permutation) {
+ var oldLeadIndex = this.leadIndex;
+
+ var oldSelectedIndexes = this.selectedIndexes;
+ this.selectedIndexes = oldSelectedIndexes.map(function(oldIndex) {
+ return permutation[oldIndex];
+ }).filter(function(index) {
+ return index != -1;
+ });
+
+ if (oldLeadIndex != -1)
+ this.leadIndex = permutation[oldLeadIndex];
+ },
+
+ /**
+ * Adjusts selection model length.
+ * @param {number} length New selection model length.
+ */
+ adjustLength: function(length) {
+ this.length_ = length;
+ }
+ };
+
+ return {
+ ListSelectionModel: ListSelectionModel
+ };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_single_selection_model.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_single_selection_model.js
new file mode 100644
index 0000000..2cf43a92
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_single_selection_model.js
@@ -0,0 +1,221 @@
+// Copyright (c) 2011 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 only
+ * allows a single index to be selected.
+ *
+ * @param {number=} opt_length The number items in the selection.
+ *
+ * @constructor
+ * @extends {!cr.EventTarget}
+ */
+ function ListSingleSelectionModel(opt_length) {
+ this.length_ = opt_length || 0;
+ this.selectedIndex = -1;
+ }
+
+ ListSingleSelectionModel.prototype = {
+ __proto__: EventTarget.prototype,
+
+ /**
+ * The number of items in the model.
+ * @type {number}
+ */
+ get length() {
+ return this.length_;
+ },
+
+ /**
+ * @type {!Array} The selected indexes.
+ */
+ get selectedIndexes() {
+ var i = this.selectedIndex;
+ return i != -1 ? [this.selectedIndex] : [];
+ },
+
+ /**
+ * Convenience getter which returns the first selected index.
+ * @type {number}
+ */
+ get selectedIndex() {
+ return this.selectedIndex_;
+ },
+ set selectedIndex(selectedIndex) {
+ var oldSelectedIndex = this.selectedIndex;
+ var i = Math.max(-1, Math.min(this.length_ - 1, selectedIndex));
+
+ if (i != oldSelectedIndex) {
+ this.beginChange();
+ this.selectedIndex_ = i
+ this.endChange();
+ }
+ },
+
+ /**
+ * Selects a range of indexes, starting with {@code start} and ends with
+ * {@code end}.
+ * @param {number} start The first index to select.
+ * @param {number} end The last index to select.
+ */
+ selectRange: function(start, end) {
+ // Only select first index.
+ this.selectedIndex = Math.min(start, end);
+ },
+
+ /**
+ * Selects all indexes.
+ */
+ selectAll: function() {
+ // Select all is not allowed on a single selection model
+ },
+
+ /**
+ * Clears the selection
+ */
+ clear: function() {
+ this.beginChange();
+ this.length_ = 0;
+ this.selectedIndex = this.anchorIndex = this.leadIndex = -1;
+ this.endChange();
+ },
+
+ /**
+ * Unselects all selected items.
+ */
+ unselectAll: function() {
+ this.selectedIndex = -1;
+ },
+
+ /**
+ * Sets the selected state for an index.
+ * @param {number} index The index to set the selected state for.
+ * @param {boolean} b Whether to select the index or not.
+ */
+ setIndexSelected: function(index, b) {
+ // Only allow selection
+ var oldSelected = index == this.selectedIndex_;
+ if (oldSelected == b)
+ return;
+
+ if (b)
+ this.selectedIndex = index;
+ else if (index == this.selectedIndex_)
+ this.selectedIndex = -1;
+ },
+
+ /**
+ * Whether a given index is selected or not.
+ * @param {number} index The index to check.
+ * @return {boolean} Whether an index is selected.
+ */
+ getIndexSelected: function(index) {
+ return index == this.selectedIndex_;
+ },
+
+ /**
+ * This is used to begin batching changes. Call {@code endChange} when you
+ * are done making changes.
+ */
+ beginChange: function() {
+ if (!this.changeCount_) {
+ this.changeCount_ = 0;
+ this.selectedIndexBefore_ = this.selectedIndex_;
+ }
+ this.changeCount_++;
+ },
+
+ /**
+ * Call this after changes are done and it will dispatch a change event if
+ * any changes were actually done.
+ */
+ endChange: function() {
+ this.changeCount_--;
+ if (!this.changeCount_) {
+ if (this.selectedIndexBefore_ != this.selectedIndex_) {
+ var e = new Event('change');
+ var indexes = [this.selectedIndexBefore_, this.selectedIndex_];
+ e.changes = indexes.filter(function(index) {
+ return index != -1;
+ }).map(function(index) {
+ return {
+ index: index,
+ selected: index == this.selectedIndex_
+ };
+ }, this);
+ this.dispatchEvent(e);
+ }
+ }
+ },
+
+ leadIndex_: -1,
+
+ /**
+ * The leadIndex is used with multiple selection and it is the index that
+ * the user is moving using the arrow keys.
+ * @type {number}
+ */
+ get leadIndex() {
+ return this.leadIndex_;
+ },
+ set leadIndex(leadIndex) {
+ var li = Math.max(-1, Math.min(this.length_ - 1, leadIndex));
+ if (li != this.leadIndex_) {
+ var oldLeadIndex = this.leadIndex_;
+ this.leadIndex_ = li;
+ cr.dispatchPropertyChange(this, 'leadIndex', li, oldLeadIndex);
+ cr.dispatchPropertyChange(this, 'anchorIndex', li, oldLeadIndex);
+ }
+ },
+
+ /**
+ * The anchorIndex is used with multiple selection.
+ * @type {number}
+ */
+ get anchorIndex() {
+ return this.leadIndex;
+ },
+ set anchorIndex(anchorIndex) {
+ this.leadIndex = anchorIndex;
+ },
+
+ /**
+ * Whether the selection model supports multiple selected items.
+ * @type {boolean}
+ */
+ get multiple() {
+ return false;
+ },
+
+ /**
+ * Adjusts the selection after reordering of items in the table.
+ * @param {!Array.<number>} permutation The reordering permutation.
+ */
+ adjustToReordering: function(permutation) {
+ if (this.leadIndex != -1)
+ this.leadIndex = permutation[this.leadIndex];
+
+ var oldSelectedIndex = this.selectedIndex;
+ if (oldSelectedIndex != -1) {
+ this.selectedIndex = permutation[oldSelectedIndex];
+ }
+ },
+
+ /**
+ * Adjusts selection model length.
+ * @param {number} length New selection model length.
+ */
+ adjustLength: function(length) {
+ this.length_ = length;
+ }
+ };
+
+ return {
+ ListSingleSelectionModel: ListSingleSelectionModel
+ };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/util.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/util.js
new file mode 100644
index 0000000..1efbf19
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/util.js
@@ -0,0 +1,151 @@
+// Copyright (c) 2011 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.
+ * @type {!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 + '")';
+}
+
+/**
+ * Parses query parameters from Location.
+ * @param {string} s The URL to generate the CSS url for.
+ * @return {object} Dictionary containing name value pairs for URL
+ */
+function parseQueryParams(location) {
+ var params = {};
+ var query = unescape(location.search.substring(1));
+ var vars = query.split("&");
+ for (var i=0; i < vars.length; i++) {
+ var pair = vars[i].split("=");
+ params[pair[0]] = pair[1];
+ }
+ return params;
+}
+
+function findAncestorByClass(el, className) {
+ return findAncestor(el, function(el) {
+ if (el.classList)
+ return el.classList.contains(className);
+ return null;
+ });
+}
+
+/**
+ * Return the first ancestor for which the {@code predicate} returns true.
+ * @param {Node} node The node to check.
+ * @param {function(Node) : boolean} predicate The function that tests the
+ * nodes.
+ * @return {Node} The found ancestor or null if not found.
+ */
+function findAncestor(node, predicate) {
+ var last = false;
+ while (node != null && !(last = predicate(node))) {
+ node = node.parentNode;
+ }
+ return last ? node : null;
+}
+
+function swapDomNodes(a, b) {
+ var afterA = a.nextSibling;
+ if (afterA == b) {
+ swapDomNodes(b, a);
+ return;
+ }
+ var aParent = a.parentNode;
+ b.parentNode.replaceChild(a, b);
+ aParent.insertBefore(b, afterA);
+}
+
+/**
+ * Disables text selection and dragging.
+ */
+function disableTextSelectAndDrag() {
+ // Disable text selection.
+ document.onselectstart = function(e) {
+ e.preventDefault();
+ }
+
+ // Disable dragging.
+ document.ondragstart = function(e) {
+ e.preventDefault();
+ }
+}
+
+// Handle click on a link. If the link points to a chrome: or file: url, then
+// call into the browser to do the navigation.
+document.addEventListener('click', function(e) {
+ // Allow preventDefault to work.
+ if (!e.returnValue)
+ return;
+
+ var el = e.target;
+ if (el.nodeType == Node.ELEMENT_NODE &&
+ el.webkitMatchesSelector('A, A *')) {
+ while (el.tagName != 'A') {
+ el = el.parentElement;
+ }
+
+ if ((el.protocol == 'file:' || el.protocol == 'about:') &&
+ (e.button == 0 || e.button == 1)) {
+ chrome.send('navigateToUrl', [
+ el.href,
+ el.target,
+ e.button,
+ e.altKey,
+ e.ctrlKey,
+ e.metaKey,
+ e.shiftKey
+ ]);
+ e.preventDefault();
+ }
+ }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/main.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/main.js
new file mode 100644
index 0000000..1b61566
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/main.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2011 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.
+
+document.addEventListener('DOMContentLoaded', function() {
+ chrome.contentSettings.plugins.getResourceIdentifiers(function(r) {
+ if (chrome.extension.lastError) {
+ $('error').textContent =
+ "Error: " + chrome.extension.lastError.message;
+ return;
+ }
+ var pluginList = $('plugin-list');
+ pluginSettings.ui.PluginList.decorate(pluginList);
+ pluginList.dataModel = new cr.ui.ArrayDataModel(r);
+ });
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list.js
new file mode 100644
index 0000000..53d9da8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list.js
@@ -0,0 +1,220 @@
+// Copyright (c) 2011 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('pluginSettings.ui', function() {
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+ const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ /**
+ * Returns the item's height, like offsetHeight but such that it works better
+ * when the page is zoomed. See the similar calculation in @{code cr.ui.List}.
+ * This version also accounts for the animation done in this file.
+ * @param {Element} item The item to get the height of.
+ * @return {number} The height of the item, calculated with zooming in mind.
+ */
+ function getItemHeight(item) {
+ var height = item.style.height;
+ // Use the fixed animation target height if set, in case the element is
+ // currently being animated and we'd get an intermediate height below.
+ if (height && height.substr(-2) == 'px')
+ return parseInt(height.substr(0, height.length - 2));
+ return item.getBoundingClientRect().height;
+ }
+
+ /**
+ * Creates a new plug-in list item element.
+ * @param {PluginList} list The plug-in list containing this item.
+ * @param {Object} info Information about the plug-in.
+ * @constructor
+ * @extends {cr.ui.ListItem}
+ */
+ function PluginListItem(list, info) {
+ var el = cr.doc.createElement('li');
+ el.list_ = list;
+ el.info_ = info;
+ el.__proto__ = PluginListItem.prototype;
+ el.decorate();
+ return el;
+ }
+
+ PluginListItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ /**
+ * The plug-in list containing this item.
+ * @type {PluginList}
+ * @private
+ */
+ list_: null,
+
+ /**
+ * Information about the plug-in.
+ * @type {Object}
+ * @private
+ */
+ info_: null,
+
+ /**
+ * The element containing details about the plug-in.
+ * @type {HTMLDivElemebt}
+ * @private
+ */
+ detailsElement_: null,
+
+ /**
+ * Initializes the element.
+ */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+
+ var info = this.info_;
+
+ var contentElement = this.ownerDocument.createElement('div');
+
+ var titleEl = this.ownerDocument.createElement('div');
+ var nameEl = this.ownerDocument.createElement('span');
+ nameEl.className = 'plugin-name';
+ nameEl.textContent = info.description;
+ nameEl.title = info.description;
+ titleEl.appendChild(nameEl);
+ this.numRulesEl_ = this.ownerDocument.createElement('span');
+ this.numRulesEl_.className = 'num-rules';
+ titleEl.appendChild(this.numRulesEl_);
+ contentElement.appendChild(titleEl);
+
+ this.detailsElement_ = this.ownerDocument.createElement('div');
+ this.detailsElement_.className = 'plugin-details hidden';
+
+ var columnHeadersEl = this.ownerDocument.createElement('div');
+ columnHeadersEl.className = 'column-headers';
+ var patternColumnEl = this.ownerDocument.createElement('div');
+ patternColumnEl.textContent =
+ chrome.i18n.getMessage("patternColumnHeader");
+ patternColumnEl.className = 'pattern-column-header';
+ var settingColumnEl = this.ownerDocument.createElement('div');
+ settingColumnEl.textContent =
+ chrome.i18n.getMessage("settingColumnHeader");
+ settingColumnEl.className = 'setting-column-header';
+ columnHeadersEl.appendChild(patternColumnEl);
+ columnHeadersEl.appendChild(settingColumnEl);
+ this.detailsElement_.appendChild(columnHeadersEl);
+ contentElement.appendChild(this.detailsElement_);
+
+ this.appendChild(contentElement);
+
+ var settings = new pluginSettings.Settings(this.info_.id);
+ this.updateRulesCount_(settings);
+ settings.addEventListener('change',
+ this.updateRulesCount_.bind(this, settings));
+
+ // Create the rule list asynchronously, to make sure that it is already
+ // fully integrated in the DOM tree.
+ window.setTimeout(this.loadRules_.bind(this, settings), 0);
+ },
+
+ /**
+ * Create the list of content setting rules applying to this plug-in.
+ * @private
+ */
+ loadRules_: function(settings) {
+ var rulesEl = this.ownerDocument.createElement('list');
+ this.detailsElement_.appendChild(rulesEl);
+
+ pluginSettings.ui.RuleList.decorate(rulesEl);
+ rulesEl.setPluginSettings(settings);
+ },
+
+ updateRulesCount_: function(settings) {
+ this.numRulesEl_.textContent = '(' + settings.getAll().length + ' rules)';
+ },
+
+ /**
+ * Whether this item is expanded or not.
+ * @type {boolean}
+ */
+ expanded_: false,
+ get expanded() {
+ return this.expanded_;
+ },
+ set expanded(expanded) {
+ if (this.expanded_ == expanded)
+ return;
+ this.expanded_ = expanded;
+ if (expanded) {
+ var oldExpanded = this.list_.expandItem;
+ this.list_.expandItem = this;
+ this.detailsElement_.classList.remove('hidden');
+ if (oldExpanded)
+ oldExpanded.expanded = false;
+ this.classList.add('plugin-show-details');
+ } else {
+ if (this.list_.expandItem == this) {
+ this.list_.leadItemHeight = 0;
+ this.list_.expandItem = null;
+ }
+ this.style.height = '';
+ this.detailsElement_.classList.add('hidden');
+ this.classList.remove('plugin-show-details');
+ }
+ },
+ };
+
+ /**
+ * Creates a new plug-in list.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var PluginList = cr.ui.define('list');
+
+ PluginList.prototype = {
+ __proto__: List.prototype,
+
+ /**
+ * Initializes the element.
+ */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+ this.classList.add('plugin-list');
+ var sm = new ListSingleSelectionModel();
+ sm.addEventListener('change', this.handleSelectionChange_.bind(this));
+ this.selectionModel = sm;
+ this.autoExpands = true;
+ },
+
+ /**
+ * Creates a new plug-in list item.
+ * @param {Object} info Information about the plug-in.
+ */
+ createItem: function(info) {
+ return new PluginListItem(this, info);
+ },
+
+ /**
+ * Called when the selection changes.
+ * @private
+ */
+ handleSelectionChange_: function(ce) {
+ ce.changes.forEach(function(change) {
+ var listItem = this.getListItemByIndex(change.index);
+ if (listItem) {
+ if (!change.selected) {
+ // TODO(bsmith) explain window timeout (from cookies_list.js)
+ window.setTimeout(function() {
+ if (!listItem.selected || !listItem.lead)
+ listItem.expanded = false;
+ }, 0);
+ } else if (listItem.lead) {
+ listItem.expanded = true;
+ }
+ }
+ }, this);
+ },
+ };
+
+ return {
+ PluginList: PluginList,
+ PluginListItem: PluginListItem,
+ };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings.js
new file mode 100644
index 0000000..9929e89
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings.js
@@ -0,0 +1,188 @@
+// Copyright (c) 2011 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('pluginSettings', function() {
+ const EventTarget = cr.EventTarget;
+ const Event = cr.Event;
+
+ /**
+ * Creates a new content settings model.
+ * @param {string} plugin Identifies the plug-in for which this object stores
+ * settings.
+ * @constructor
+ * @extends {cr.EventTarget}
+ */
+ function Settings(plugin) {
+ this.plugin_ = plugin;
+ }
+
+ Settings.prototype = {
+ __proto__: cr.EventTarget.prototype,
+
+ /**
+ * Identifies the plug-in for which this object stores settings.
+ * @type {string}
+ * @private
+ */
+ plugin_: null,
+
+ /**
+ * Clears all content settings, and recreates them from local storage. If a
+ * content setting can't be set (which shouldn't really happen, as it has
+ * been successfully set previously), it is removed from local storage as
+ * well.
+ * @param {function()} callback Called when the content settings have been
+ * recreated, or on error.
+ * @private
+ */
+ recreateRules_: function(callback) {
+ chrome.contentSettings.plugins.clear({}, function() {
+ if (chrome.extension.lastError) {
+ console.error("Error clearing rules");
+ callback();
+ return;
+ }
+ var length = window.localStorage.length;
+ if (length == 0) {
+ callback();
+ return;
+ }
+ var count = length;
+ var errors = [];
+ for (var i = 0; i < length; i++) {
+ var key = window.localStorage.key(i);
+ var keyArray = JSON.parse(key);
+ var plugin = keyArray[0];
+ var pattern = keyArray[1];
+ var setting = window.localStorage.getItem(key);
+ chrome.contentSettings.plugins.set({
+ 'primaryPattern': pattern,
+ 'resourceIdentifier': { 'id': plugin },
+ 'setting': setting,
+ }, function() {
+ if (chrome.extension.lastError) {
+ console.error('Error restoring [' + plugin_ + ', ' +
+ pattern + setting + ']: ' +
+ chrome.extension.lastError.message);
+ window.localStorage.removeItem(key);
+ }
+ count--;
+ if (count == 0)
+ callback();
+ });
+ }
+ });
+ },
+
+ /**
+ * Creates a content setting rule and calls the passed in callback with the
+ * result.
+ * @param {string} pattern The content setting pattern for the rule.
+ * @param {string} setting The setting for the rule.
+ * @param {function(?string)} callback Called when the content settings have
+ * been updated, or on error.
+ */
+ set: function(pattern, setting, callback) {
+ var plugin = this.plugin_;
+ chrome.contentSettings.plugins.set({
+ 'primaryPattern': pattern,
+ 'resourceIdentifier': { 'id': plugin },
+ 'setting': setting,
+ }, function() {
+ if (chrome.extension.lastError) {
+ callback(chrome.extension.lastError.message);
+ } else {
+ window.localStorage.setItem(JSON.stringify([plugin, pattern]),
+ setting);
+ callback();
+ }
+ });
+ },
+
+ /**
+ * Removes the content setting rule with a given pattern, and calls the
+ * passed in callback afterwards.
+ * @param {string} pattern The content setting pattern for the rule.
+ * @param {function()?} callback Called when the content settings have
+ * been updated.
+ */
+ clear: function(pattern, callback) {
+ window.localStorage.removeItem(
+ JSON.stringify([this.plugin_, pattern]));
+ this.recreateRules_(callback);
+ },
+
+ /**
+ * Updates the content setting rule with a given pattern to a new pattern
+ * and setting and calls the passed in callback with the result.
+ * @param {string} oldPattern The old content setting pattern for the rule.
+ * @param {string} newPattern The new content setting pattern for the rule.
+ * @param {string} setting The setting for the rule.
+ * @param {function(?string)} callback Called when the content settings have
+ * been updated, or on error.
+ */
+ update: function(oldPattern, newPattern, setting, callback) {
+ if (oldPattern == newPattern) {
+ // Avoid recreating all rules if only the setting changed.
+ this.set(newPattern, setting, callback);
+ return;
+ }
+ var oldSetting = this.get(oldPattern);
+ var settings = this;
+ // Remove the old rule.
+ this.clear(oldPattern, function() {
+ // Try to set the new rule.
+ settings.set(newPattern, setting, function(error) {
+ if (error) {
+ // If setting the new rule failed, restore the old rule.
+ settings.setInternal_(oldPattern, oldSetting,
+ function(restoreError) {
+ if (restoreError) {
+ console.error('Error restoring [' + settings.plugin_ + ', ' +
+ oldPattern + oldSetting + ']: ' + restoreError);
+ }
+ callback(error);
+ });
+ } else {
+ callback();
+ }
+ });
+ });
+ },
+
+ /**
+ * Returns the content setting for a given pattern.
+ * @param {string} pattern The content setting pattern for the rule.
+ * @return {string} The setting for the rule.
+ */
+ get: function(primaryPattern) {
+ return window.localStorage.getItem(
+ JSON.stringify([this.plugin_, primaryPattern]));
+ },
+
+ /**
+ * @return {array} A list of all content setting rules for this plug-in.
+ */
+ getAll: function() {
+ var length = window.localStorage.length;
+ var rules = [];
+ for (var i = 0; i < length; i++) {
+ var key = window.localStorage.key(i);
+ var keyArray = JSON.parse(key);
+ if (keyArray[0] == this.plugin_) {
+ rules.push({
+ 'primaryPattern': keyArray[1],
+ 'setting': window.localStorage.getItem(key),
+ });
+ }
+ }
+ return rules;
+ }
+ };
+
+ return {
+ Settings: Settings,
+ }
+});
+
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list.js
new file mode 100644
index 0000000..db83cee
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list.js
@@ -0,0 +1,392 @@
+// Copyright (c) 2011 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('pluginSettings.ui', function() {
+ const InlineEditableItemList = options.InlineEditableItemList;
+ const InlineEditableItem = options.InlineEditableItem;
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+
+ /**
+ * Creates a new rule list item.
+ * @param {RuleList} list The rule list containing this item.
+ * @param {Object} rule The content setting rule.
+ * @constructor
+ * @extends {options.InlineEditableItem}
+ */
+ function RuleListItem(list, rule) {
+ var el = cr.doc.createElement('li');
+
+ el.dataItem_ = rule;
+ el.list_ = list;
+ el.__proto__ = RuleListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ RuleListItem.prototype = {
+ __proto__: InlineEditableItem.prototype,
+
+ /**
+ * The content setting rule.
+ * @type {Object}
+ * @private
+ */
+ dataItem_: null,
+
+ /**
+ * The rule list containing this item.
+ * @type {RuleList}
+ * @private
+ */
+ list_: null,
+
+ /**
+ * The text input element for the pattern.
+ * @type {HTMLInputElement}
+ * @private
+ */
+ input_: null,
+
+ /**
+ * The popup button for the setting.
+ * @type {HTMLSelectElement}
+ * @private
+ */
+ select_: null,
+
+ /**
+ * The static text field containing the pattern.
+ * @type {HTMLDivElement}
+ * @private
+ */
+ patternLabel_: null,
+
+ /**
+ * The static text field containing the setting.
+ * @type {HTMLDivElement}
+ * @private
+ */
+ settingLabel_: null,
+
+ /**
+ * Called when an element is decorated as a list item.
+ */
+ decorate: function() {
+ InlineEditableItem.prototype.decorate.call(this);
+
+ this.isPlaceholder = !this.pattern;
+ var patternCell = this.createEditableTextCell(this.pattern);
+ patternCell.className = 'rule-pattern';
+ patternCell.classList.add('weakrtl');
+ this.contentElement.appendChild(patternCell);
+ var input = patternCell.querySelector('input');
+ if (this.pattern)
+ this.patternLabel_ = patternCell.querySelector('.static-text');
+ else
+ input.placeholder = chrome.i18n.getMessage("addNewPattern");
+
+ // TODO(stuartmorgan): Create an createEditableSelectCell abstracting
+ // this code.
+ // Setting label for display mode. |pattern| will be null for the 'add new
+ // exception' row.
+ if (this.pattern) {
+ var settingLabel = cr.doc.createElement('span');
+ settingLabel.textContent = this.settingForDisplay();
+ settingLabel.className = 'rule-behavior';
+ settingLabel.setAttribute('displaymode', 'static');
+ this.contentElement.appendChild(settingLabel);
+ this.settingLabel_ = settingLabel;
+ }
+
+ // Setting select element for edit mode.
+ var select = cr.doc.createElement('select');
+ var optionAllow = cr.doc.createElement('option');
+ optionAllow.textContent = chrome.i18n.getMessage("allowRule");
+ optionAllow.value = 'allow';
+ select.appendChild(optionAllow);
+
+ var optionBlock = cr.doc.createElement('option');
+ optionBlock.textContent = chrome.i18n.getMessage("blockRule");
+ optionBlock.value = 'block';
+ select.appendChild(optionBlock);
+
+ this.contentElement.appendChild(select);
+ select.className = 'rule-behavior';
+ if (this.pattern)
+ select.setAttribute('displaymode', 'edit');
+
+ this.input_ = input;
+ this.select_ = select;
+
+ this.updateEditables();
+
+ // Listen for edit events.
+ this.addEventListener('canceledit', this.onEditCancelled_);
+ this.addEventListener('commitedit', this.onEditCommitted_);
+ },
+
+ /**
+ * The pattern (e.g., a URL) for the rule.
+ * @type {string}
+ */
+ get pattern() {
+ return this.dataItem_['primaryPattern'];
+ },
+ set pattern(pattern) {
+ this.dataItem_['primaryPattern'] = pattern;
+ },
+
+ /**
+ * The setting (allow/block) for the rule.
+ * @type {string}
+ */
+ get setting() {
+ return this.dataItem_['setting'];
+ },
+ set setting(setting) {
+ this.dataItem_['setting'] = setting;
+ },
+
+ /**
+ * Gets a human-readable setting string.
+ * @type {string}
+ */
+ settingForDisplay: function() {
+ var setting = this.setting;
+ if (setting == 'allow')
+ return chrome.i18n.getMessage("allowRule");
+ else if (setting == 'block')
+ return chrome.i18n.getMessage("blockRule");
+ },
+
+ /**
+ * Set the <input> to its original contents. Used when the user quits
+ * editing.
+ */
+ resetInput: function() {
+ this.input_.value = this.pattern;
+ },
+
+ /**
+ * Copy the data model values to the editable nodes.
+ */
+ updateEditables: function() {
+ this.resetInput();
+
+ var settingOption =
+ this.select_.querySelector('[value=\'' + this.setting + '\']');
+ if (settingOption)
+ settingOption.selected = true;
+ },
+
+ /** @inheritDoc */
+ get hasBeenEdited() {
+ var livePattern = this.input_.value;
+ var liveSetting = this.select_.value;
+ return livePattern != this.pattern || liveSetting != this.setting;
+ },
+
+ /**
+ * Called when committing an edit.
+ * @param {Event} e The end event.
+ * @private
+ */
+ onEditCommitted_: function(e) {
+ var newPattern = this.input_.value;
+ var newSetting = this.select_.value;
+
+ this.finishEdit(newPattern, newSetting);
+ },
+
+ /**
+ * Called when cancelling an edit; resets the control states.
+ * @param {Event} e The cancel event.
+ * @private
+ */
+ onEditCancelled_: function() {
+ this.updateEditables();
+ },
+
+ /**
+ * Editing is complete; update the model.
+ * @param {string} newPattern The pattern that the user entered.
+ * @param {string} newSetting The setting the user chose.
+ */
+ finishEdit: function(newPattern, newSetting) {
+ this.patternLabel_.textContent = newPattern;
+ this.settingLabel_.textContent = this.settingForDisplay();
+ var oldPattern = this.pattern;
+ this.pattern = newPattern;
+ this.setting = newSetting;
+
+ this.list_.settings.update(oldPattern, newPattern, newSetting,
+ this.list_.settingsChangedCallback());
+ }
+ };
+
+ /**
+ * Create a new list item to add a rule.
+ * @param {RuleList} list The rule list containing this item.
+ * @constructor
+ * @extends {AddRuleListItem}
+ */
+ function AddRuleListItem(list) {
+ var el = cr.doc.createElement('div');
+ el.dataItem_ = {};
+ el.list_ = list;
+ el.__proto__ = AddRuleListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ AddRuleListItem.prototype = {
+ __proto__: RuleListItem.prototype,
+
+ /**
+ * Initializes the element.
+ */
+ decorate: function() {
+ RuleListItem.prototype.decorate.call(this);
+
+ this.setting = 'allow';
+ },
+
+ /**
+ * Clear the <input> and let the placeholder text show again.
+ */
+ resetInput: function() {
+ this.input_.value = '';
+ },
+
+ /** @inheritDoc */
+ get hasBeenEdited() {
+ return this.input_.value != '';
+ },
+
+ /**
+ * Editing is complete; update the model. As long as the pattern isn't
+ * empty, we'll just add it.
+ * @param {string} newPattern The pattern that the user entered.
+ * @param {string} newSetting The setting the user chose.
+ */
+ finishEdit: function(newPattern, newSetting) {
+ this.resetInput();
+ this.list_.settings.set(newPattern, newSetting,
+ this.list_.settingsChangedCallback());
+ },
+ };
+
+ /**
+ * Creates a new rule list.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var RuleList = cr.ui.define('list');
+
+ RuleList.prototype = {
+ __proto__: InlineEditableItemList.prototype,
+
+ /**
+ * The content settings model for this list.
+ * @type {Settings}
+ */
+ settings: null,
+
+ /**
+ * Called when an element is decorated as a list.
+ */
+ decorate: function() {
+ InlineEditableItemList.prototype.decorate.call(this);
+
+ this.classList.add('rule-list');
+
+ this.autoExpands = true;
+ this.reset();
+ },
+
+ /**
+ * Creates an item to go in the list.
+ * @param {Object} entry The element from the data model for this row.
+ */
+ createItem: function(entry) {
+ if (entry) {
+ return new RuleListItem(this, entry);
+ } else {
+ var addRuleItem = new AddRuleListItem(this);
+ addRuleItem.deletable = false;
+ return addRuleItem;
+ }
+ },
+
+ /**
+ * Sets the rules in the js model.
+ * @param {Object} entries A list of dictionaries of values, each dictionary
+ * represents a rule.
+ */
+ setRules_: function(entries) {
+ var deleteCount = this.dataModel.length - 1;
+
+ var args = [0, deleteCount];
+ args.push.apply(args, entries);
+ this.dataModel.splice.apply(this.dataModel, args);
+ },
+
+ /**
+ * Called when the list of content setting rules has been changed.
+ * @param {?string} error The error message, if an error occurred.
+ * Otherwise, this is null.
+ * @private
+ */
+ settingsChanged_: function(error) {
+ if (error)
+ $('error').textContent = 'Error: ' + error;
+ else
+ $('error').textContent = '';
+ this.setRules_(this.settings.getAll());
+ },
+
+ /**
+ * @return {function()} A bound callback to update the UI after the settings
+ * have been changed.
+ */
+ settingsChangedCallback: function() {
+ return this.settingsChanged_.bind(this);
+ },
+
+ /**
+ * Binds this list to the content settings model.
+ * @param {Settings} settings The content settings model.
+ */
+ setPluginSettings: function(settings) {
+ this.settings = settings;
+ this.settingsChanged_();
+ },
+
+ /**
+ * Removes all rules from the js model.
+ */
+ reset: function() {
+ // The null creates the Add New Rule row.
+ this.dataModel = new ArrayDataModel([null]);
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ var listItem = this.getListItemByIndex(index);
+ if (listItem.undeletable)
+ return;
+
+ this.settings.clear(listItem.pattern, this.settingsChangedCallback());
+ },
+ };
+
+ return {
+ RuleListItem: RuleListItem,
+ AddRuleListItem: AddRuleListItem,
+ RuleList: RuleList,
+ }
+});
+
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json b/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json
new file mode 100644
index 0000000..c42e0ef
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json
@@ -0,0 +1,16 @@
+{
+ "name" : "__MSG_extName__",
+ "version" : "0.6",
+ "description" : "__MSG_extDescription__",
+ "options_page": "options.html",
+ "permissions": [
+ "contentSettings"
+ ],
+ "icons": {
+ "128": "bunny128.png",
+ "48": "bunny48.png"
+ },
+ "minimum_chrome_version": "16.0.912",
+ "content_security_policy": "default-src 'none'; script-src 'self'; img-src 'self'; style-src 'self'",
+ "default_locale": "en"
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options.html b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options.html
new file mode 100644
index 0000000..abfdd7e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<link rel="stylesheet" href="domui/css/button.css">
+<link rel="stylesheet" href="domui/css/chrome_shared.css">
+<link rel="stylesheet" href="domui/css/list.css">
+<link rel="stylesheet" href="domui/css/select.css">
+
+<link rel="stylesheet" href="options/css/list.css">
+
+<link rel="stylesheet" href="css/plugin_list.css">
+<link rel="stylesheet" href="css/rule_list.css">
+
+<script src="domui/js/cr.js"></script>
+<script src="domui/js/cr/event_target.js"></script>
+<script src="domui/js/cr/ui.js"></script>
+<script src="domui/js/cr/ui/array_data_model.js"></script>
+<script src="domui/js/cr/ui/list_item.js"></script>
+<script src="domui/js/cr/ui/list_selection_controller.js"></script>
+<script src="domui/js/cr/ui/list_selection_model.js"></script>
+<script src="domui/js/cr/ui/list_single_selection_model.js"></script>
+<script src="domui/js/cr/ui/list.js"></script>
+<script src="domui/js/util.js"></script>
+
+<script src="options/js/deletable_item_list.js"></script>
+<script src="options/js/inline_editable_list.js"></script>
+
+<script src="js/plugin_list.js" type="text/javascript"></script>
+<script src="js/plugin_settings.js" type="text/javascript"></script>
+<script src="js/rule_list.js" type="text/javascript"></script>
+
+<script src="js/main.js" type="text/javascript"></script>
+</head>
+<body>
+<list id="plugin-list"></list>
+<div id="error"></div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/css/list.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/css/list.css
new file mode 100644
index 0000000..9cc716d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/css/list.css
@@ -0,0 +1,124 @@
+/*
+Copyright (c) 2011 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.
+*/
+
+.raw-button,
+.raw-button:hover,
+.raw-button:active {
+ -webkit-box-shadow: none;
+ background-color: transparent;
+ background-repeat: no-repeat;
+ border: none;
+ min-width: 0;
+ padding: 1px 6px;
+}
+
+list > * {
+ -webkit-box-align: center;
+ -webkit-transition: .15s background-color;
+ box-sizing: border-box;
+ border-radius: 0;
+ display: -webkit-box;
+ height: 32px;
+ border: none;
+ margin: 0;
+}
+
+list:not([disabled]) > :hover {
+ background-color: #e4ecf7;
+}
+
+/* TODO(stuartmorgan): Once this becomes the list style for other WebUI pages
+ * these rules can be simplified (since they wont need to override other rules).
+ */
+
+list:not([hasElementFocus]) > [selected],
+list:not([hasElementFocus]) > [lead][selected] {
+ background-color: #d0d0d0;
+ background-image: none;
+}
+
+list[hasElementFocus] > [selected],
+list[hasElementFocus] > [lead][selected],
+list:not([hasElementFocus]) > [selected]:hover,
+list:not([hasElementFocus]) > [selected][lead]:hover {
+ background-color: #bbcee9;
+ background-image: none;
+}
+
+list[disabled] {
+ opacity: 0.6;
+}
+
+list > .heading {
+ color: #666666;
+}
+
+list > .heading:hover {
+ background-color: transparent;
+ border-color: transparent;
+}
+
+list .deletable-item {
+ -webkit-box-align: center;
+}
+
+list .deletable-item > :first-child {
+ -webkit-box-align: center;
+ -webkit-box-flex: 1;
+ -webkit-padding-end: 5px;
+ display: -webkit-box;
+}
+
+list .close-button {
+ -webkit-transition: .15s opacity;
+ background-color: transparent;
+ /* TODO(stuartmorgan): Replace with real images once they are available. */
+ background-image: url("../images/close_bar.png");
+ border: none;
+ display: block;
+ height: 16px;
+ opacity: 1;
+ width: 16px;
+}
+
+list > *:not(:hover):not([lead]) .close-button,
+list > *:not(:hover):not([selected]) .close-button,
+list:not([hasElementFocus]) > *:not(:hover) .close-button,
+list[disabled] .close-button,
+list .close-button[disabled] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+list .close-button:hover {
+ background-image: url("../images/close_bar_h.png");
+}
+
+list .close-button:active {
+ background-image: url("../images/close_bar_p.png");
+}
+
+list .static-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+list[inlineeditable] input {
+ box-sizing: border-box;
+ margin: 0;
+ width: 100%;
+}
+
+list[inlineeditable] > :not([editing]) [displaymode="edit"],
+list[inlineeditable] > [editing] [displaymode="static"] {
+ display: none;
+}
+
+list > [editing] input:invalid {
+ /* TODO(stuartmorgan): Replace with validity badge */
+ background-color: pink;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar.png
new file mode 100644
index 0000000..912df05d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_h.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_h.png
new file mode 100644
index 0000000..c5e1481
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_h.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_p.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_p.png
new file mode 100644
index 0000000..cc5bbbe
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_p.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/deletable_item_list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/deletable_item_list.js
new file mode 100644
index 0000000..4d2e68e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/deletable_item_list.js
@@ -0,0 +1,185 @@
+// Copyright (c) 2011 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('options', function() {
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+
+ /**
+ * Creates a deletable list item, which has a button that will trigger a call
+ * to deleteItemAtIndex(index) in the list.
+ */
+ var DeletableItem = cr.ui.define('li');
+
+ DeletableItem.prototype = {
+ __proto__: ListItem.prototype,
+
+ /**
+ * The element subclasses should populate with content.
+ * @type {HTMLElement}
+ * @private
+ */
+ contentElement_: null,
+
+ /**
+ * The close button element.
+ * @type {HTMLElement}
+ * @private
+ */
+ closeButtonElement_: null,
+
+ /**
+ * Whether or not this item can be deleted.
+ * @type {boolean}
+ * @private
+ */
+ deletable_: true,
+
+ /** @inheritDoc */
+ decorate: function() {
+ ListItem.prototype.decorate.call(this);
+
+ this.classList.add('deletable-item');
+
+ this.contentElement_ = this.ownerDocument.createElement('div');
+ this.appendChild(this.contentElement_);
+
+ this.closeButtonElement_ = this.ownerDocument.createElement('button');
+ this.closeButtonElement_.classList.add('raw-button');
+ this.closeButtonElement_.classList.add('close-button');
+ this.closeButtonElement_.addEventListener('mousedown',
+ this.handleMouseDownUpOnClose_);
+ this.closeButtonElement_.addEventListener('mouseup',
+ this.handleMouseDownUpOnClose_);
+ this.closeButtonElement_.addEventListener('focus',
+ this.handleFocus_.bind(this));
+ this.appendChild(this.closeButtonElement_);
+ },
+
+ /**
+ * Returns the element subclasses should add content to.
+ * @return {HTMLElement} The element subclasses should popuplate.
+ */
+ get contentElement() {
+ return this.contentElement_;
+ },
+
+ /* Gets/sets the deletable property. An item that is not deletable doesn't
+ * show the delete button (although space is still reserved for it).
+ */
+ get deletable() {
+ return this.deletable_;
+ },
+ set deletable(value) {
+ this.deletable_ = value;
+ this.closeButtonElement_.disabled = !value;
+ },
+
+ /**
+ * Called when a focusable child element receives focus. Selects this item
+ * in the list selection model.
+ * @private
+ */
+ handleFocus_: function() {
+ var list = this.parentNode;
+ var index = list.getIndexOfListItem(this);
+ list.selectionModel.selectedIndex = index;
+ list.selectionModel.anchorIndex = index;
+ },
+
+ /**
+ * Don't let the list have a crack at the event. We don't want clicking the
+ * close button to change the selection of the list.
+ * @param {Event} e The mouse down/up event object.
+ * @private
+ */
+ handleMouseDownUpOnClose_: function(e) {
+ if (!e.target.disabled)
+ e.stopPropagation();
+ },
+ };
+
+ var DeletableItemList = cr.ui.define('list');
+
+ DeletableItemList.prototype = {
+ __proto__: List.prototype,
+
+ /** @inheritDoc */
+ decorate: function() {
+ List.prototype.decorate.call(this);
+ this.addEventListener('click', this.handleClick_);
+ this.addEventListener('keydown', this.handleKeyDown_);
+ },
+
+ /**
+ * Callback for onclick events.
+ * @param {Event} e The click event object.
+ * @private
+ */
+ handleClick_: function(e) {
+ if (this.disabled)
+ return;
+
+ var target = e.target;
+ if (target.classList.contains('close-button')) {
+ var listItem = this.getListItemAncestor(target);
+ var selected = this.selectionModel.selectedIndexes;
+
+ // Check if the list item that contains the close button being clicked
+ // is not in the list of selected items. Only delete this item in that
+ // case.
+ var idx = this.getIndexOfListItem(listItem);
+ if (selected.indexOf(idx) == -1) {
+ this.deleteItemAtIndex(idx);
+ } else {
+ this.deleteSelectedItems_();
+ }
+ }
+ },
+
+ /**
+ * Callback for keydown events.
+ * @param {Event} e The keydown event object.
+ * @private
+ */
+ handleKeyDown_: function(e) {
+ // Map delete (and backspace on Mac) to item deletion (unless focus is
+ // in an input field, where it's intended for text editing).
+ if ((e.keyCode == 46 || (e.keyCode == 8 && cr.isMac)) &&
+ e.target.tagName != 'INPUT') {
+ this.deleteSelectedItems_();
+ // Prevent the browser from going back.
+ e.preventDefault();
+ }
+ },
+
+ /**
+ * Deletes all the currently selected items that are deletable.
+ * @private
+ */
+ deleteSelectedItems_: function() {
+ var selected = this.selectionModel.selectedIndexes;
+ // Reverse through the list of selected indexes to maintain the
+ // correct index values after deletion.
+ for (var j = selected.length - 1; j >= 0; j--) {
+ var index = selected[j];
+ if (this.getListItemByIndex(index).deletable)
+ this.deleteItemAtIndex(index);
+ }
+ },
+
+ /**
+ * Called when an item should be deleted; subclasses are responsible for
+ * implementing.
+ * @param {number} index The index of the item that is being deleted.
+ */
+ deleteItemAtIndex: function(index) {
+ },
+ };
+
+ return {
+ DeletableItemList: DeletableItemList,
+ DeletableItem: DeletableItem,
+ };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/inline_editable_list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/inline_editable_list.js
new file mode 100644
index 0000000..8aed93b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/inline_editable_list.js
@@ -0,0 +1,414 @@
+// Copyright (c) 2011 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('options', function() {
+ const DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+
+ /**
+ * Creates a new list item with support for inline editing.
+ * @constructor
+ * @extends {options.DeletableListItem}
+ */
+ function InlineEditableItem() {
+ var el = cr.doc.createElement('div');
+ InlineEditableItem.decorate(el);
+ return el;
+ }
+
+ /**
+ * Decorates an element as a inline-editable list item. Note that this is
+ * a subclass of DeletableItem.
+ * @param {!HTMLElement} el The element to decorate.
+ */
+ InlineEditableItem.decorate = function(el) {
+ el.__proto__ = InlineEditableItem.prototype;
+ el.decorate();
+ };
+
+ InlineEditableItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * Whether or not this item can be edited.
+ * @type {boolean}
+ * @private
+ */
+ editable_: true,
+
+ /**
+ * Whether or not this is a placeholder for adding a new item.
+ * @type {boolean}
+ * @private
+ */
+ isPlaceholder_: false,
+
+ /**
+ * Fields associated with edit mode.
+ * @type {array}
+ * @private
+ */
+ editFields_: null,
+
+ /**
+ * Whether or not the current edit should be considered cancelled, rather
+ * than committed, when editing ends.
+ * @type {boolean}
+ * @private
+ */
+ editCancelled_: true,
+
+ /**
+ * The editable item corresponding to the last click, if any. Used to decide
+ * initial focus when entering edit mode.
+ * @type {HTMLElement}
+ * @private
+ */
+ editClickTarget_: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ this.editFields_ = [];
+ this.addEventListener('mousedown', this.handleMouseDown_);
+ this.addEventListener('keydown', this.handleKeyDown_);
+ this.addEventListener('leadChange', this.handleLeadChange_);
+ },
+
+ /** @inheritDoc */
+ selectionChanged: function() {
+ this.updateEditState();
+ },
+
+ /**
+ * Called when this element gains or loses 'lead' status. Updates editing
+ * mode accordingly.
+ * @private
+ */
+ handleLeadChange_: function() {
+ this.updateEditState();
+ },
+
+ /**
+ * Updates the edit state based on the current selected and lead states.
+ */
+ updateEditState: function() {
+ if (this.editable)
+ this.editing = this.selected && this.lead;
+ },
+
+ /**
+ * Whether the user is currently editing the list item.
+ * @type {boolean}
+ */
+ get editing() {
+ return this.hasAttribute('editing');
+ },
+ set editing(editing) {
+ if (this.editing == editing)
+ return;
+
+ if (editing)
+ this.setAttribute('editing', '');
+ else
+ this.removeAttribute('editing');
+
+ if (editing) {
+ this.editCancelled_ = false;
+
+ cr.dispatchSimpleEvent(this, 'edit', true);
+
+ var focusElement = this.editClickTarget_ || this.initialFocusElement;
+ this.editClickTarget_ = null;
+
+ // When this is called in response to the selectedChange event,
+ // the list grabs focus immediately afterwards. Thus we must delay
+ // our focus grab.
+ var self = this;
+ if (focusElement) {
+ window.setTimeout(function() {
+ // Make sure we are still in edit mode by the time we execute.
+ if (self.editing) {
+ focusElement.focus();
+ focusElement.select();
+ }
+ }, 50);
+ }
+ } else {
+ if (!this.editCancelled_ && this.hasBeenEdited &&
+ this.currentInputIsValid) {
+ if (this.isPlaceholder)
+ this.parentNode.focusPlaceholder = true;
+
+ this.updateStaticValues_();
+ cr.dispatchSimpleEvent(this, 'commitedit', true);
+ } else {
+ this.resetEditableValues_();
+ cr.dispatchSimpleEvent(this, 'canceledit', true);
+ }
+ }
+ },
+
+ /**
+ * Whether the item is editable.
+ * @type {boolean}
+ */
+ get editable() {
+ return this.editable_;
+ },
+ set editable(editable) {
+ this.editable_ = editable;
+ if (!editable)
+ this.editing = false;
+ },
+
+ /**
+ * Whether the item is a new item placeholder.
+ * @type {boolean}
+ */
+ get isPlaceholder() {
+ return this.isPlaceholder_;
+ },
+ set isPlaceholder(isPlaceholder) {
+ this.isPlaceholder_ = isPlaceholder;
+ if (isPlaceholder)
+ this.deletable = false;
+ },
+
+ /**
+ * The HTML element that should have focus initially when editing starts,
+ * if a specific element wasn't clicked.
+ * Defaults to the first <input> element; can be overriden by subclasses if
+ * a different element should be focused.
+ * @type {HTMLElement}
+ */
+ get initialFocusElement() {
+ return this.contentElement.querySelector('input');
+ },
+
+ /**
+ * Whether the input in currently valid to submit. If this returns false
+ * when editing would be submitted, either editing will not be ended,
+ * or it will be cancelled, depending on the context.
+ * Can be overrided by subclasses to perform input validation.
+ * @type {boolean}
+ */
+ get currentInputIsValid() {
+ return true;
+ },
+
+ /**
+ * Returns true if the item has been changed by an edit.
+ * Can be overrided by subclasses to return false when nothing has changed
+ * to avoid unnecessary commits.
+ * @type {boolean}
+ */
+ get hasBeenEdited() {
+ return true;
+ },
+
+ /**
+ * Returns a div containing an <input>, as well as static text if
+ * isPlaceholder is not true.
+ * @param {string} text The text of the cell.
+ * @return {HTMLElement} The HTML element for the cell.
+ * @private
+ */
+ createEditableTextCell: function(text) {
+ var container = this.ownerDocument.createElement('div');
+
+ if (!this.isPlaceholder) {
+ var textEl = this.ownerDocument.createElement('div');
+ textEl.className = 'static-text';
+ textEl.textContent = text;
+ textEl.setAttribute('displaymode', 'static');
+ container.appendChild(textEl);
+ }
+
+ var inputEl = this.ownerDocument.createElement('input');
+ inputEl.type = 'text';
+ inputEl.value = text;
+ if (!this.isPlaceholder) {
+ inputEl.setAttribute('displaymode', 'edit');
+ inputEl.staticVersion = textEl;
+ } else {
+ // At this point |this| is not attached to the parent list yet, so give
+ // a short timeout in order for the attachment to occur.
+ var self = this;
+ window.setTimeout(function() {
+ var list = self.parentNode;
+ if (list && list.focusPlaceholder) {
+ list.focusPlaceholder = false;
+ if (list.shouldFocusPlaceholder())
+ inputEl.focus();
+ }
+ }, 50);
+ }
+
+ inputEl.addEventListener('focus', this.handleFocus_.bind(this));
+ container.appendChild(inputEl);
+ this.editFields_.push(inputEl);
+
+ return container;
+ },
+
+ /**
+ * Resets the editable version of any controls created by createEditable*
+ * to match the static text.
+ * @private
+ */
+ resetEditableValues_: function() {
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ var staticLabel = editFields[i].staticVersion;
+ if (!staticLabel && !this.isPlaceholder)
+ continue;
+
+ if (editFields[i].tagName == 'INPUT') {
+ editFields[i].value =
+ this.isPlaceholder ? '' : staticLabel.textContent;
+ }
+ // Add more tag types here as new createEditable* methods are added.
+
+ editFields[i].setCustomValidity('');
+ }
+ },
+
+ /**
+ * Sets the static version of any controls created by createEditable*
+ * to match the current value of the editable version. Called on commit so
+ * that there's no flicker of the old value before the model updates.
+ * @private
+ */
+ updateStaticValues_: function() {
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ var staticLabel = editFields[i].staticVersion;
+ if (!staticLabel)
+ continue;
+
+ if (editFields[i].tagName == 'INPUT')
+ staticLabel.textContent = editFields[i].value;
+ // Add more tag types here as new createEditable* methods are added.
+ }
+ },
+
+ /**
+ * Called a key is pressed. Handles committing and cancelling edits.
+ * @param {Event} e The key down event.
+ * @private
+ */
+ handleKeyDown_: function(e) {
+ if (!this.editing)
+ return;
+
+ var endEdit = false;
+ switch (e.keyIdentifier) {
+ case 'U+001B': // Esc
+ this.editCancelled_ = true;
+ endEdit = true;
+ break;
+ case 'Enter':
+ if (this.currentInputIsValid)
+ endEdit = true;
+ break;
+ }
+
+ if (endEdit) {
+ // Blurring will trigger the edit to end; see InlineEditableItemList.
+ this.ownerDocument.activeElement.blur();
+ // Make sure that handled keys aren't passed on and double-handled.
+ // (e.g., esc shouldn't both cancel an edit and close a subpage)
+ e.stopPropagation();
+ }
+ },
+
+ /**
+ * Called when the list item is clicked. If the click target corresponds to
+ * an editable item, stores that item to focus when edit mode is started.
+ * @param {Event} e The mouse down event.
+ * @private
+ */
+ handleMouseDown_: function(e) {
+ if (!this.editable || this.editing)
+ return;
+
+ var clickTarget = e.target;
+ var editFields = this.editFields_;
+ for (var i = 0; i < editFields.length; i++) {
+ if (editFields[i] == clickTarget ||
+ editFields[i].staticVersion == clickTarget) {
+ this.editClickTarget_ = editFields[i];
+ return;
+ }
+ }
+ },
+ };
+
+ /**
+ * Takes care of committing changes to inline editable list items when the
+ * window loses focus.
+ */
+ function handleWindowBlurs() {
+ window.addEventListener('blur', function(e) {
+ var itemAncestor = findAncestor(document.activeElement, function(node) {
+ return node instanceof InlineEditableItem;
+ });
+ if (itemAncestor);
+ document.activeElement.blur();
+ });
+ }
+ handleWindowBlurs();
+
+ var InlineEditableItemList = cr.ui.define('list');
+
+ InlineEditableItemList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ /**
+ * Focuses the input element of the placeholder if true.
+ * @type {boolean}
+ */
+ focusPlaceholder: false,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.setAttribute('inlineeditable', '');
+ this.addEventListener('hasElementFocusChange',
+ this.handleListFocusChange_);
+ },
+
+ /**
+ * Called when the list hierarchy as a whole loses or gains focus; starts
+ * or ends editing for the lead item if necessary.
+ * @param {Event} e The change event.
+ * @private
+ */
+ handleListFocusChange_: function(e) {
+ var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
+ if (leadItem) {
+ if (e.newValue)
+ leadItem.updateEditState();
+ else
+ leadItem.editing = false;
+ }
+ },
+
+ /**
+ * May be overridden by subclasses to disable focusing the placeholder.
+ * @return true if the placeholder element should be focused on edit commit.
+ */
+ shouldFocusPlaceholder: function() {
+ return true;
+ },
+ };
+
+ // Export
+ return {
+ InlineEditableItem: InlineEditableItem,
+ InlineEditableItemList: InlineEditableItemList,
+ };
+});
diff --git a/chrome/common/extensions/docs/experimental.savePage.html b/chrome/common/extensions/docs/experimental.savePage.html
index 5ace1e4..7a98ddc 100644
--- a/chrome/common/extensions/docs/experimental.savePage.html
+++ b/chrome/common/extensions/docs/experimental.savePage.html
@@ -426,7 +426,7 @@ permission to use it.
<div class="description">
<p class="todo" style="display: none; ">Undocumented.</p>
- <p>Saves the content of the tab with given id to MHTML.</p>
+ <p>Saves the content of the tab with given id as MHTML.</p>
<!-- PARAMETERS -->
<h4>Parameters</h4>
@@ -509,7 +509,7 @@ permission to use it.
<dd class="todo" style="display: none; ">
Undocumented.
</dd>
- <dd>The id of the tab to save to MHTML.</dd>
+ <dd>The id of the tab to save as MHTML.</dd>
<dd style="display: none; ">
This parameter was added in version
<b><span></span></b>.
diff --git a/chrome/common/extensions/docs/samples.html b/chrome/common/extensions/docs/samples.html
index 8a9e8db0..feca078 100644
--- a/chrome/common/extensions/docs/samples.html
+++ b/chrome/common/extensions/docs/samples.html
@@ -377,6 +377,7 @@
"8d0a50b57c26bb498be592e871001ffed91541b4": "PAGE ACTION BY CONTENT SHOWS A PAGE ACTION FOR HTML PAGES CONTAINING THE WORD SANDWICH BACKGROUND_PAGE PAGE_ACTION CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.PAGEACTION.SHOW",
"80b86ccc6e8520660fa591caa565826f0ed1b12c": "PAGE ACTION BY URL SHOWS A PAGE ACTION FOR URLS WHICH HAVE THE LETTER G IN THEM. BACKGROUND_PAGE PAGE_ACTION TABS CHROME.PAGEACTION.SHOW CHROME.TABS.ONUPDATED",
"d74c3c18a1c1dd18b035149105a306f837c8823e": "PAGE BENCHMARKER CHROMIUM PAGE BENCHMARKER. BACKGROUND_PAGE BROWSER_ACTION OPTIONS_PAGE TABS CHROME.BROWSERACTION.ONCLICKED CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETTITLE CHROME.EXTENSION.CONNECT CHROME.EXTENSION.GETBACKGROUNDPAGE CHROME.EXTENSION.GETEXTENSIONTABS CHROME.EXTENSION.GETURL CHROME.EXTENSION.ONCONNECT CHROME.TABS.CREATE CHROME.TABS.EXECUTESCRIPT CHROME.TABS.GET CHROME.TABS.GETALLINWINDOW CHROME.TABS.GETSELECTED CHROME.TABS.REMOVE CHROME.TABS.UPDATE CHROME.WINDOWS.GET CHROME.WINDOWS.GETCURRENT",
+ "ab4b9e00a673701b355db9eb8f9ebf2c850cd784": "PER-PLUGIN CONTENT SETTINGS CUSTOMIZE YOUR CONTENT SETTING FOR DIFFERENT PLUG-INS. CONTENTSETTINGS OPTIONS_PAGE CHROME.I18N.GETMESSAGE",
"e6ae17ab4ccfd7e059c8c01f25760ca5d894c7fd": "PRINT THIS PAGE ADDS A PRINT BUTTON TO THE BROWSER. BACKGROUND_PAGE BROWSER_ACTION TABS CHROME.BROWSERACTION.ONCLICKED CHROME.TABS.UPDATE",
"beff6ecd9677dea0a7c648c5042165b48bb66f09": "PROCESS MONITOR ADDS A BROWSER ACTION THAT MONITORS RESOURCE USAGE OF ALL BROWSER PROCESSES. BROWSER_ACTION EXPERIMENTAL POPUP TABS",
"3e8e226d87e431296bb110b4f6eb7eec2ca7a826": "PROXY EXTENSION API SAMPLE SET CHROME-SPECIFIC PROXIES; A DEMONSTRATION OF CHROMES PROXY API BACKGROUND_PAGE BROWSER_ACTION POPUP PROXY CHROME.BROWSERACTION.SETBADGEBACKGROUNDCOLOR CHROME.BROWSERACTION.SETBADGETEXT CHROME.BROWSERACTION.SETTITLE CHROME.EXTENSION.ISALLOWEDINCOGNITOACCESS CHROME.EXTENSION.ONREQUEST CHROME.EXTENSION.SENDREQUEST CHROME.I18N.GETMESSAGE CHROME.PROXY.ONPROXYERROR",
@@ -3348,6 +3349,95 @@
- <a>Install extension</a>
</span>
</div>
+</div><div class="sample" id="ab4b9e00a673701b355db9eb8f9ebf2c850cd784">
+ <img class="icon" src="examples/extensions/plugin_settings/bunny128.png">
+ <img class="icon" src="images/sample-default-icon.png" style="display: none; ">
+ <h2 class="name">
+ <a href="#ab4b9e00a673701b355db9eb8f9ebf2c850cd784">Per-plugin content settings</a>
+ <span style="display: none; ">(packaged app)</span>
+ </h2>
+ <p class="metadata features">Uses
+ <span>
+ <strong>contentSettings</strong><span style="display: none; ">, </span>
+ <span> and</span>
+ </span><span>
+ <strong>options_page</strong><span style="display: none; ">, </span>
+ <span style="display: none; "> and</span>
+ </span>
+ </p>
+ <p>Customize your content setting for different plug-ins.</p>
+ <div class="apicalls"><strong>Calls:</strong>
+ <ul>
+ <li>
+ <code><a href="i18n.html#method-getMessage">chrome.i18n.getMessage</a></code>
+ </li>
+ </ul>
+ </div>
+ <div class="sourcefiles"><strong>Source files:</strong>
+ <ul>
+ <li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json?content-type=text/plain">_locales/en/messages.json</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/plugin_list.css?content-type=text/plain">css/plugin_list.css</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/rule_list.css?content-type=text/plain">css/rule_list.css</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/button.css?content-type=text/plain">domui/css/button.css</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/chrome_shared.css?content-type=text/plain">domui/css/chrome_shared.css</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/list.css?content-type=text/plain">domui/css/list.css</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/select.css?content-type=text/plain">domui/css/select.css</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr.js?content-type=text/plain">domui/js/cr.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/event_target.js?content-type=text/plain">domui/js/cr/event_target.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui.js?content-type=text/plain">domui/js/cr/ui.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/array_data_model.js?content-type=text/plain">domui/js/cr/ui/array_data_model.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list.js?content-type=text/plain">domui/js/cr/ui/list.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_item.js?content-type=text/plain">domui/js/cr/ui/list_item.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_controller.js?content-type=text/plain">domui/js/cr/ui/list_selection_controller.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_model.js?content-type=text/plain">domui/js/cr/ui/list_selection_model.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_single_selection_model.js?content-type=text/plain">domui/js/cr/ui/list_single_selection_model.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/util.js?content-type=text/plain">domui/js/util.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/main.js?content-type=text/plain">js/main.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list.js?content-type=text/plain">js/plugin_list.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings.js?content-type=text/plain">js/plugin_settings.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list.js?content-type=text/plain">js/rule_list.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json?content-type=text/plain">manifest.json</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/options.html?content-type=text/plain">options.html</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/css/list.css?content-type=text/plain">options/css/list.css</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/deletable_item_list.js?content-type=text/plain">options/js/deletable_item_list.js</a></code>
+ </li><li>
+ <code><a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/inline_editable_list.js?content-type=text/plain">options/js/inline_editable_list.js</a></code>
+ </li>
+ </ul>
+ </div>
+ <div>
+ <a target="_blank" href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/plugin_settings/">Browse source</a>
+ - <a href="examples/extensions/plugin_settings.zip">Download source</a>
+ <!-- Only show the Install CRX link if a CRX file is provided -->
+ <span style="display: none; ">
+ - <a>Install extension</a>
+ </span>
+ </div>
</div><div class="sample" id="e6ae17ab4ccfd7e059c8c01f25760ca5d894c7fd">
<img class="icon" style="display: none; ">
<img class="icon" src="images/sample-default-icon.png">
diff --git a/chrome/common/extensions/docs/samples.json b/chrome/common/extensions/docs/samples.json
index dd45316..3771549 100644
--- a/chrome/common/extensions/docs/samples.json
+++ b/chrome/common/extensions/docs/samples.json
@@ -1845,6 +1845,54 @@
},
{
"api_calls": [
+ "chrome.i18n.getMessage"
+ ],
+ "crx_path": null,
+ "description": "Customize your content setting for different plug-ins.",
+ "features": [
+ "contentSettings",
+ "options_page"
+ ],
+ "icon": "bunny128.png",
+ "id": "ab4b9e00a673701b355db9eb8f9ebf2c850cd784",
+ "name": "Per-plugin content settings",
+ "packaged_app": false,
+ "path": "examples\/extensions\/plugin_settings\/",
+ "protocols": [],
+ "search_string": "PER-PLUGIN CONTENT SETTINGS CUSTOMIZE YOUR CONTENT SETTING FOR DIFFERENT PLUG-INS. CONTENTSETTINGS OPTIONS_PAGE CHROME.I18N.GETMESSAGE",
+ "source_files": [
+ "_locales\/en\/messages.json",
+ "css\/plugin_list.css",
+ "css\/rule_list.css",
+ "domui\/css\/button.css",
+ "domui\/css\/chrome_shared.css",
+ "domui\/css\/list.css",
+ "domui\/css\/select.css",
+ "domui\/js\/cr.js",
+ "domui\/js\/cr\/event_target.js",
+ "domui\/js\/cr\/ui.js",
+ "domui\/js\/cr\/ui\/array_data_model.js",
+ "domui\/js\/cr\/ui\/list.js",
+ "domui\/js\/cr\/ui\/list_item.js",
+ "domui\/js\/cr\/ui\/list_selection_controller.js",
+ "domui\/js\/cr\/ui\/list_selection_model.js",
+ "domui\/js\/cr\/ui\/list_single_selection_model.js",
+ "domui\/js\/util.js",
+ "js\/main.js",
+ "js\/plugin_list.js",
+ "js\/plugin_settings.js",
+ "js\/rule_list.js",
+ "manifest.json",
+ "options.html",
+ "options\/css\/list.css",
+ "options\/js\/deletable_item_list.js",
+ "options\/js\/inline_editable_list.js"
+ ],
+ "source_hash": "1e2765f821fa09cf0db9cce20ae61d964565986b",
+ "zip_path": "examples\/extensions\/plugin_settings.zip"
+ },
+ {
+ "api_calls": [
"chrome.browserAction.onClicked",
"chrome.tabs.update"
],