diff --git a/Documentation/js-api.txt b/Documentation/js-api.txt
index 4ef2a6ce8d..d909c00b26 100644
--- a/Documentation/js-api.txt
+++ b/Documentation/js-api.txt
@@ -713,10 +713,12 @@ accessed through this name.
[[Gerrit_css]]
=== Gerrit.css()
+[WARNING]
+This method is deprecated. It doesn't work with Shadow DOM and
+will be removed in the future. Please, use link:pg-plugin-dev.html#plugin-styles[plugin.styles] instead.
+
Creates a new unique CSS class and injects it into the document.
The name of the class is returned and can be used by the plugin.
-See link:#Gerrit_html[`Gerrit.html()`] for an easy way to use
-generated class names.
Classes created with this function should be created once at install
time and reused throughout the plugin. Repeatedly creating the same
@@ -814,112 +816,6 @@ If the URL passed matches `http://...`, `https://...`, or `//...`
the current browser window will navigate to the non-Gerrit URL.
The user can return to Gerrit with the back button.
-[[Gerrit_html]]
-=== Gerrit.html()
-Parses an HTML fragment after performing template replacements. If
-the HTML has a single root element or node that node is returned,
-otherwise it is wrapped inside a `
` and the div is returned.
-
-.Signature
-[source,javascript]
-----
-Gerrit.html(htmlText, options, wantElements);
-----
-
-* htmlText: string of HTML to be parsed. A new unattached `
` is
- created in the browser's document and the innerHTML property is
- assigned to the passed string, after performing replacements. If
- the div has exactly one child, that child will be returned instead
- of the div.
-
-* options: optional object reference supplying replacements for any
- `{name}` references in htmlText. Navigation through objects is
- supported permitting `{style.bar}` to be replaced with `"foo"` if
- options was `{style: {bar: "foo"}}`. Value replacements are HTML
- escaped before being inserted into the document fragment.
-
-* wantElements: if options is given and wantElements is also true
- an object consisting of `{root: parsedElement, elements: {...}}` is
- returned instead of the parsed element. The elements object contains
- a property for each element using `id={name}` in htmlText.
-
-.Example
-[source,javascript]
-----
-var style = {bar: Gerrit.css('background: yellow')};
-Gerrit.html(
- 'Hello {name}!',
- {style: style, name: "World"});
-----
-
-Event handlers can be automatically attached to elements referenced
-through an attribute id. Object navigation is not supported for ids,
-and the parser strips the id attribute before returning the result.
-Handler functions must begin with `on` and be a function to be
-installed on the element. This approach is useful for onclick and
-other handlers that do not want to create circular references that
-will eventually leak browser memory.
-
-.Example
-[source,javascript]
-----
-var options = {
- link: {
- onclick: function(e) { window.close() },
- },
-};
-Gerrit.html('Close', options);
-----
-
-When using options to install handlers care must be taken to not
-accidentally include the returned element into the event handler's
-closure. This is why options is built before calling `Gerrit.html()`
-and not inline as a shown above with "Hello World".
-
-DOM nodes can optionally be returned, allowing handlers to access the
-elements identified by `id={name}` at a later point in time.
-
-.Example
-[source,javascript]
-----
-var w = Gerrit.html(
- '
Name:
'
- + '
Age:
'
- + '',
- {
- submit: {
- onclick: function(s) {
- var e = w.elements;
- window.alert(e.name.value + " is " + e.age.value);
- },
- },
- }, true);
-----
-
-To prevent memory leaks `w.root` and `w.elements` should be set to
-null when the elements are no longer necessary. Screens can use
-link:#screen_onUnload[screen.onUnload()] to define a callback function
-to perform this cleanup:
-
-[source,javascript]
-----
-var w = Gerrit.html(...);
-screen.body.appendElement(w.root);
-screen.onUnload(function() { w.clear() });
-----
-
-[[Gerrit_injectCss]]
-=== Gerrit.injectCss()
-Injects CSS rules into the document by appending onto the end of the
-existing rule list. CSS rules are global to the entire application
-and must be manually scoped by each plugin. For an automatic scoping
-alternative see link:#Gerrit_css[`css()`].
-
-[source,javascript]
-----
-Gerrit.injectCss('.myplugin_bg {background: #000}');
-----
-
[[Gerrit_install]]
=== Gerrit.install()
Registers a new plugin by invoking the supplied initialization
diff --git a/Documentation/pg-plugin-dev.txt b/Documentation/pg-plugin-dev.txt
index 8fb5655214..d90185147e 100644
--- a/Documentation/pg-plugin-dev.txt
+++ b/Documentation/pg-plugin-dev.txt
@@ -360,6 +360,16 @@ screen.
Deprecated. Use link:#plugin-settings[`plugin.settings()`] instead.
+[[plugin-styles]]
+=== styles
+`plugin.styles()`
+
+.Params:
+- none
+
+.Returns:
+- Instance of link:pg-plugin-styles-api.html[GrStylesApi]
+
=== changeMetadata
`plugin.changeMetadata()`
@@ -372,6 +382,7 @@ Deprecated. Use link:#plugin-settings[`plugin.settings()`] instead.
=== theme
`plugin.theme()`
+
Note: TODO
=== url
diff --git a/Documentation/pg-plugin-style-object.txt b/Documentation/pg-plugin-style-object.txt
new file mode 100644
index 0000000000..cdcfb557d7
--- /dev/null
+++ b/Documentation/pg-plugin-style-object.txt
@@ -0,0 +1,33 @@
+= Gerrit Code Review - GrStyleObject
+
+Store information about css style properties. You can't create this object
+directly. Instead you should use the link:pg-plugin-styles-api.html#css[css] method.
+This object allows to apply style correctly to elements within different shadow
+subtree.
+
+[[get-class-name]]
+== getClassName
+`styleObject.getClassName(element)`
+
+.Params
+- `element` - an HTMLElement.
+
+.Returns
+- `string` - class name. The class name is valid only within the shadow root of `element`.
+
+Creates a new unique CSS class and injects it into the appropriate place
+in DOM (it can be document or shadow root for element). This class can be later
+added to the element or to any other element in the same shadow root. It is guarantee,
+that method adds CSS class only once for each shadow root.
+
+== apply
+`styleObject.apply(element)`
+
+.Params
+- `element` - element to apply style.
+
+Create a new unique CSS class (see link:#get-class-name[getClassName]) and
+adds class to the element.
+
+
+
diff --git a/Documentation/pg-plugin-styles-api.txt b/Documentation/pg-plugin-styles-api.txt
new file mode 100644
index 0000000000..a8293251ec
--- /dev/null
+++ b/Documentation/pg-plugin-styles-api.txt
@@ -0,0 +1,29 @@
+= Gerrit Code Review - Plugin styles API
+
+This API is provided by link:pg-plugin-dev.html#plugin-styles[plugin.styles()]
+and provides a way to apply dynamically created styles to elements in a
+document.
+
+[[css]]
+== css
+`styles.css(rulesStr)`
+
+.Params
+- `*string* rulesStr` string with CSS styling declarations.
+
+Example:
+----
+const styleObject = plugin.styles().css('background: black; color: white;');
+...
+const className = styleObject.getClassName(element)
+...
+element.classList.add(className);
+...
+styleObject.apply(someOtherElement);
+----
+
+.Returns
+- Instance of link:pg-plugin-style-object.html[GrStyleObject].
+
+
+
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html
new file mode 100644
index 0000000000..74b87c8241
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html
@@ -0,0 +1,18 @@
+
+
+
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js
new file mode 100644
index 0000000000..879b3921b9
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js
@@ -0,0 +1,77 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function(window) {
+ 'use strict';
+
+ // Prevent redefinition.
+ if (window.GrStylesApi) { return; }
+
+ let styleObjectCount = 0;
+
+ function GrStyleObject(rulesStr) {
+ this._rulesStr = rulesStr;
+ this._className = `__pg_js_api_class_${styleObjectCount}`;
+ styleObjectCount++;
+ }
+
+ /**
+ * Creates a new unique CSS class and injects it in a root node of the element
+ * if it hasn't been added yet. A root node is an document or is the
+ * associated shadowRoot. This class can be added to any element with the same
+ * root node.
+ * @param {HTMLElement} element The element to get class name for.
+ * @return {string} Appropriate class name for the element is returned
+ */
+ GrStyleObject.prototype.getClassName = function(element) {
+ const rootNode = Polymer.Settings.useShadow
+ ? element.getRootNode() : document.body;
+ if (!rootNode.__pg_js_api_style_tags) {
+ rootNode.__pg_js_api_style_tags = {};
+ }
+ if (!rootNode.__pg_js_api_style_tags[this._className]) {
+ const styleTag = document.createElement('style');
+ styleTag.innerHTML = `.${this._className} { ${this._rulesStr} }`;
+ rootNode.appendChild(styleTag);
+ rootNode.__pg_js_api_style_tags[this._className] = true;
+ }
+ return this._className;
+ };
+
+ /**
+ * Apply shared style to the element.
+ * @param {HTMLElement} element The element to apply style for
+ */
+ GrStyleObject.prototype.apply = function(element) {
+ element.classList.add(this.getClassName(element));
+ };
+
+
+ function GrStylesApi() {
+ }
+
+ /**
+ * Creates a new GrStyleObject with specified style properties.
+ * @param {string} String with style properties.
+ * @return {GrStyleObject}
+ */
+ GrStylesApi.prototype.css = function(ruleStr) {
+ return new GrStyleObject(ruleStr);
+ };
+
+
+ window.GrStylesApi = GrStylesApi;
+})(window);
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
new file mode 100644
index 0000000000..d67a3092bd
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
@@ -0,0 +1,182 @@
+
+
+
+
+gr-admin-api
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js
index 45f28d1998..76cb51f2f7 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js
@@ -43,25 +43,26 @@
* Method to add annotations to a content line.
* @param {number} offset The char offset where the update starts.
* @param {number} length The number of chars that the update covers.
- * @param {string} cssClass The name of a CSS class created using Gerrit.css.
+ * @param {GrStyleObject} styleObject The style object for the range.
* @param {string} side The side of the update. ('left' or 'right')
*/
GrAnnotationActionsContext.prototype.annotateRange = function(
- offset, length, cssClass, side) {
+ offset, length, styleObject, side) {
if (this._contentEl && this._contentEl.getAttribute('data-side') == side) {
- GrAnnotation.annotateElement(this._contentEl, offset, length, cssClass);
+ GrAnnotation.annotateElement(this._contentEl, offset, length,
+ styleObject.getClassName(this._contentEl));
}
};
/**
* Method to add a CSS class to the line number TD element.
- * @param {string} cssClass The name of a CSS class created using Gerrit.css.
+ * @param {GrStyleObject} styleObject The style object for the range.
* @param {string} side The side of the update. ('left' or 'right')
*/
GrAnnotationActionsContext.prototype.annotateLineNumber = function(
- cssClass, side) {
+ styleObject, side) {
if (this._lineNumberEl && this._lineNumberEl.classList.contains(side)) {
- this._lineNumberEl.classList.add(cssClass);
+ styleObject.apply(this._lineNumberEl);
}
};
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
index 653999c7cf..2a6487ed8a 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
@@ -42,9 +42,13 @@ limitations under the License.
let sandbox;
let el;
let lineNumberEl;
+ let plugin;
setup(() => {
sandbox = sinon.sandbox.create();
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+
const str = 'lorem ipsum blah blah';
const line = {text: str};
el = document.createElement('div');
@@ -64,32 +68,34 @@ limitations under the License.
annotateElementSpy = sandbox.spy(GrAnnotation, 'annotateElement');
const start = 0;
const end = 100;
- const cssClass = Gerrit.css('background-color: #000000');
+ const cssStyleObject = plugin.styles().css('background-color: #000000');
// Assert annotateElement is not called when side is different.
- instance.annotateRange(start, end, cssClass, 'left');
+ instance.annotateRange(start, end, cssStyleObject, 'left');
assert.equal(annotateElementSpy.callCount, 0);
// Assert annotateElement is called once when side is the same.
- instance.annotateRange(start, end, cssClass, 'right');
+ instance.annotateRange(start, end, cssStyleObject, 'right');
assert.equal(annotateElementSpy.callCount, 1);
const args = annotateElementSpy.getCalls()[0].args;
assert.equal(args[0], el);
assert.equal(args[1], start);
assert.equal(args[2], end);
- assert.equal(args[3], cssClass);
+ assert.equal(args[3], cssStyleObject.getClassName(el));
});
test('test annotateLineNumber', () => {
- const cssClass = Gerrit.css('background-color: #000000');
+ const cssStyleObject = plugin.styles().css('background-color: #000000');
+
+ const className = cssStyleObject.getClassName(lineNumberEl);
// Assert that css class is *not* applied when side is different.
- instance.annotateLineNumber(cssClass, 'left');
- assert.isFalse(lineNumberEl.classList.contains(cssClass));
+ instance.annotateLineNumber(cssStyleObject, 'left');
+ assert.isFalse(lineNumberEl.classList.contains(className));
// Assert that css class is applied when side is the same.
- instance.annotateLineNumber(cssClass, 'right');
- assert.isTrue(lineNumberEl.classList.contains(cssClass));
+ instance.annotateLineNumber(cssStyleObject, 'right');
+ assert.isTrue(lineNumberEl.classList.contains(className));
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
index e04867ac3f..e460660bdf 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
@@ -26,6 +26,7 @@ limitations under the License.
+
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index fff2e3320e..2dd0e09516 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -319,6 +319,10 @@
return new GrSettingsApi(this);
};
+ Plugin.prototype.styles = function() {
+ return new GrStylesApi();
+ };
+
/**
* To make REST requests for plugin-provided endpoints, use
* @example
@@ -511,7 +515,13 @@
'Please use plugin.getPluginName() instead.');
};
+ /**
+ * @deprecated Use plugin.styles().css(rulesStr) instead. Please, consult
+ * the documentation how to replace it accordingly.
+ */
Gerrit.css = function(rulesStr) {
+ console.warn('Gerrit.css(rulesStr) is deprecated!',
+ 'Use plugin.styles().css(rulesStr)');
if (!Gerrit._customStyleSheet) {
const styleEl = document.createElement('style');
document.head.appendChild(styleEl);
diff --git a/polygerrit-ui/app/samples/coverage-plugin.html b/polygerrit-ui/app/samples/coverage-plugin.html
index f8b5560573..f5f9c6e787 100644
--- a/polygerrit-ui/app/samples/coverage-plugin.html
+++ b/polygerrit-ui/app/samples/coverage-plugin.html
@@ -32,6 +32,11 @@
const coverageData = {};
let displayCoverage = false;
const annotationApi = plugin.annotationApi();
+ const styleApi = plugin.styles();
+
+ const coverageStyle = styleApi.css('background-color: #EF9B9B !important');
+ const emptyStyle = styleApi.css('');
+
annotationApi.addLayer(context => {
if (Object.keys(coverageData).length === 0) {
// Coverage data is not ready yet.
@@ -41,16 +46,16 @@
const line = context.line;
// Highlight lines missing coverage with this background color if
// coverage should be displayed, else do nothing.
- const cssClass = displayCoverage
- ? Gerrit.css('background-color: #EF9B9B')
- : Gerrit.css('');
+ const annotationStyle = displayCoverage
+ ? coverageStyle
+ : emptyStyle;
if (coverageData[path] &&
coverageData[path].changeNum === context.changeNum &&
coverageData[path].patchNum === context.patchNum) {
const linesMissingCoverage = coverageData[path].linesMissingCoverage;
if (linesMissingCoverage.includes(line.afterNumber)) {
- context.annotateRange(0, line.text.length, cssClass, 'right');
- context.annotateLineNumber(cssClass, 'right');
+ context.annotateRange(0, line.text.length, annotationStyle, 'right');
+ context.annotateLineNumber(annotationStyle, 'right');
}
}
}).enableToggleCheckbox('Display Coverage', checkbox => {
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 2b052a01c5..0fbc8f1388 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -125,6 +125,7 @@ limitations under the License.
'edit/gr-edit-file-controls/gr-edit-file-controls_test.html',
'edit/gr-editor-view/gr-editor-view_test.html',
'plugins/gr-admin-api/gr-admin-api_test.html',
+ 'plugins/gr-styles-api/gr-styles-api_test.html',
'plugins/gr-attribute-helper/gr-attribute-helper_test.html',
'plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html',
'plugins/gr-event-helper/gr-event-helper_test.html',