diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 987d43bfee..69b1e140bf 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -50,6 +50,8 @@ drupal:
dependencies:
- core/domready
- core/drupalSettings
+ drupalSettings:
+ suppressDeprecationErrors: true
drupalSettings:
version: VERSION
diff --git a/core/misc/drupal.es6.js b/core/misc/drupal.es6.js
index ea47bb790c..c4a6b4ce8a 100644
--- a/core/misc/drupal.es6.js
+++ b/core/misc/drupal.es6.js
@@ -42,7 +42,7 @@ window.Drupal = { behaviors: {}, locale: {} };
// JavaScript should be made compatible with libraries other than jQuery by
// wrapping it in an anonymous closure.
-(function(Drupal, drupalSettings, drupalTranslations) {
+(function(Drupal, drupalSettings, drupalTranslations, console, Proxy, Reflect) {
/**
* Helper to rethrow errors asynchronously.
*
@@ -541,6 +541,61 @@ window.Drupal = { behaviors: {}, locale: {} };
return window.encodeURIComponent(item).replace(/%2F/g, '/');
};
+ /**
+ * Triggers deprecation error.
+ *
+ * Deprecation errors are only triggered if deprecation errors haven't
+ * been suppressed.
+ *
+ * @param {Object} deprecation
+ * The deprecation options.
+ * @param {string} deprecation.message
+ * The deprecation message.
+ *
+ * @see https://www.drupal.org/core/deprecation#javascript
+ */
+ Drupal.deprecationError = ({ message }) => {
+ if (
+ drupalSettings.suppressDeprecationErrors === false &&
+ typeof console !== 'undefined' &&
+ console.warn
+ ) {
+ console.warn(`[Deprecation] ${message}`);
+ }
+ };
+
+ /**
+ * Triggers deprecation error when object property is being used.
+ *
+ * @param {Object} deprecation
+ * The deprecation options.
+ * @param {Object} deprecation.target
+ * The targeted object.
+ * @param {string} deprecation.deprecatedProperty
+ * A key of the deprecated property.
+ * @param {string} deprecation.message
+ * The deprecation message.
+ * @returns {Object}
+ *
+ * @see https://www.drupal.org/core/deprecation#javascript
+ */
+ Drupal.deprecatedProperty = ({ target, deprecatedProperty, message }) => {
+ // Proxy and Reflect are not supported by all browsers. Unsupported browsers
+ // are ignored since this is a development feature.
+ if (!Proxy || !Reflect) {
+ return target;
+ }
+
+ return new Proxy(target, {
+ get: (target, key, ...rest) => {
+ if (key === deprecatedProperty) {
+ Drupal.deprecationError({ message: message });
+ }
+ return Reflect.get(target, key, ...rest);
+ },
+ });
+ };
+
/**
* Generates the themed representation of a Drupal object.
*
@@ -583,4 +638,11 @@ window.Drupal = { behaviors: {}, locale: {} };
Drupal.theme.placeholder = function(str) {
return `${Drupal.checkPlain(str)}`;
};
-})(Drupal, window.drupalSettings, window.drupalTranslations);
+})(
+ Drupal,
+ window.drupalSettings,
+ window.drupalTranslations,
+ window.console,
+ window.Proxy,
+ window.Reflect,
+);
diff --git a/core/misc/drupal.js b/core/misc/drupal.js
index 4e3389358e..661d511eef 100644
--- a/core/misc/drupal.js
+++ b/core/misc/drupal.js
@@ -7,7 +7,7 @@
window.Drupal = { behaviors: {}, locale: {} };
-(function (Drupal, drupalSettings, drupalTranslations) {
+(function (Drupal, drupalSettings, drupalTranslations, console, Proxy, Reflect) {
Drupal.throwError = function (error) {
setTimeout(function () {
throw error;
@@ -173,12 +173,43 @@ window.Drupal = { behaviors: {}, locale: {} };
return window.encodeURIComponent(item).replace(/%2F/g, '/');
};
+ Drupal.deprecationError = function (_ref) {
+ var message = _ref.message;
+
+ if (drupalSettings.suppressDeprecationErrors === false && typeof console !== 'undefined' && console.warn) {
+ console.warn('[Deprecation] ' + message);
+ }
+ };
+
+ Drupal.deprecatedProperty = function (_ref2) {
+ var target = _ref2.target,
+ deprecatedProperty = _ref2.deprecatedProperty,
+ message = _ref2.message;
+
+ if (!Proxy || !Reflect) {
+ return target;
+ }
+
+ return new Proxy(target, {
+ get: function get(target, key) {
+ for (var _len = arguments.length, rest = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+ rest[_key - 2] = arguments[_key];
+ }
+
+ if (key === deprecatedProperty) {
+ Drupal.deprecationError({ message: message });
+ }
+ return Reflect.get.apply(Reflect, [target, key].concat(rest));
+ }
+ });
+ };
+
Drupal.theme = function (func) {
if (func in Drupal.theme) {
var _Drupal$theme;
- for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
- args[_key - 1] = arguments[_key];
+ for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+ args[_key2 - 1] = arguments[_key2];
}
return (_Drupal$theme = Drupal.theme)[func].apply(_Drupal$theme, args);
@@ -188,4 +219,4 @@ window.Drupal = { behaviors: {}, locale: {} };
Drupal.theme.placeholder = function (str) {
return '' + Drupal.checkPlain(str) + '';
};
-})(Drupal, window.drupalSettings, window.drupalTranslations);
\ No newline at end of file
+})(Drupal, window.drupalSettings, window.drupalTranslations, window.console, window.Proxy, window.Reflect);
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.es6.js b/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.es6.js
new file mode 100644
index 0000000000..aef44e6b77
--- /dev/null
+++ b/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.es6.js
@@ -0,0 +1,21 @@
+/**
+ * @file
+ * Testing tools for deprecating JavaScript functions and class properties.
+ */
+(function() {
+ if (typeof console !== 'undefined' && console.warn) {
+ const originalWarnFunction = console.warn;
+ console.warn = warning => {
+ let warnings = JSON.parse(
+ localStorage.getItem('js_deprecation_log_test.warnings') ||
+ JSON.stringify([]),
+ );
+ warnings.push(warning);
+ localStorage.setItem(
+ 'js_deprecation_log_test.warnings',
+ JSON.stringify(warnings),
+ );
+ originalWarnFunction(warning);
+ };
+ }
+})();
diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.js b/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.js
new file mode 100644
index 0000000000..167a1bd8e4
--- /dev/null
+++ b/core/modules/system/tests/modules/js_deprecation_log_test/js/js_deprecation_log.js
@@ -0,0 +1,18 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function () {
+ if (typeof console !== 'undefined' && console.warn) {
+ var originalWarnFunction = console.warn;
+ console.warn = function (warning) {
+ var warnings = JSON.parse(localStorage.getItem('js_deprecation_log_test.warnings') || JSON.stringify([]));
+ warnings.push(warning);
+ localStorage.setItem('js_deprecation_log_test.warnings', JSON.stringify(warnings));
+ originalWarnFunction(warning);
+ };
+ }
+})();
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.info.yml b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.info.yml
new file mode 100644
index 0000000000..b2589c6e2a
--- /dev/null
+++ b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.info.yml
@@ -0,0 +1,6 @@
+name: 'JS Deprecation log test'
+description: 'Stores all JS deprecation calls to allow JS tests to determine if they have been called.'
+type: module
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.libraries.yml b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.libraries.yml
new file mode 100644
index 0000000000..1744815c98
--- /dev/null
+++ b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.libraries.yml
@@ -0,0 +1,6 @@
+deprecation_log:
+ version: VERSION
+ js:
+ js/js_deprecation_log.js: { weight: -100 }
+ dependencies:
+ - core/drupal
diff --git a/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.module b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.module
new file mode 100644
index 0000000000..a86a2f21fb
--- /dev/null
+++ b/core/modules/system/tests/modules/js_deprecation_log_test/js_deprecation_log_test.module
@@ -0,0 +1,21 @@
+ {
+ deprecationError({ message: 'This function is deprecated for testing purposes.' });
+ };
+ const objectWithDeprecatedProperty = deprecatedProperty({
+ target: { deprecatedProperty: 'Kitten' },
+ deprecatedProperty: 'deprecatedProperty',
+ message: 'This property is deprecated for testing purposes.',
+ });
+
+ behaviors.testDeprecations = {
+ attach: () => {
+ deprecatedFunction();
+ const deprecatedProperty =
+ objectWithDeprecatedProperty.deprecatedProperty;
+ },
+ };
+})(Drupal);
diff --git a/core/modules/system/tests/modules/js_deprecation_test/js/js_deprecation_test.js b/core/modules/system/tests/modules/js_deprecation_test/js/js_deprecation_test.js
new file mode 100644
index 0000000000..b5faec0796
--- /dev/null
+++ b/core/modules/system/tests/modules/js_deprecation_test/js/js_deprecation_test.js
@@ -0,0 +1,28 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function (_ref) {
+ var deprecationError = _ref.deprecationError,
+ deprecatedProperty = _ref.deprecatedProperty,
+ behaviors = _ref.behaviors;
+
+ var deprecatedFunction = function deprecatedFunction() {
+ deprecationError({ message: 'This function is deprecated for testing purposes.' });
+ };
+ var objectWithDeprecatedProperty = deprecatedProperty({
+ target: { deprecatedProperty: 'Kitten' },
+ deprecatedProperty: 'deprecatedProperty',
+ message: 'This property is deprecated for testing purposes.'
+ });
+
+ behaviors.testDeprecations = {
+ attach: function attach() {
+ deprecatedFunction();
+ var deprecatedProperty = objectWithDeprecatedProperty.deprecatedProperty;
+ }
+ };
+})(Drupal);
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.info.yml b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.info.yml
new file mode 100644
index 0000000000..0a1b6153be
--- /dev/null
+++ b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.info.yml
@@ -0,0 +1,6 @@
+name: 'JS Deprecation test'
+description: 'Provides deprecated code that can be used for tests'
+type: module
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.libraries.yml b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.libraries.yml
new file mode 100644
index 0000000000..0ae3e7ce3c
--- /dev/null
+++ b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.libraries.yml
@@ -0,0 +1,6 @@
+deprecation_test:
+ version: VERSION
+ js:
+ js/js_deprecation_test.js: {}
+ dependencies:
+ - core/drupal
diff --git a/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.module b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.module
new file mode 100644
index 0000000000..523c286875
--- /dev/null
+++ b/core/modules/system/tests/modules/js_deprecation_test/js_deprecation_test.module
@@ -0,0 +1,13 @@
+ ['library' => ['js_deprecation_test/deprecation_test']],
+ ];
+ }
+
+}
diff --git a/core/profiles/testing/testing.info.yml b/core/profiles/testing/testing.info.yml
index 834198a743..317c4f0f9b 100644
--- a/core/profiles/testing/testing.info.yml
+++ b/core/profiles/testing/testing.info.yml
@@ -9,3 +9,4 @@ install:
# tests as possible run with them enabled.
- drupal:page_cache
- dynamic_page_cache
+ - js_deprecation_log_test
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php
new file mode 100644
index 0000000000..4714cecf72
--- /dev/null
+++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptDeprecationTest.php
@@ -0,0 +1,26 @@
+drupalGet('js_deprecation_test');
+ // Ensure that deprecation message from previous page loads will be
+ // detected.
+ $this->drupalGet('user');
+ }
+
+}
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php b/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php
index d677aa8e2d..a4f2785cb1 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php
@@ -76,8 +76,9 @@ protected function initMink() {
* {@inheritdoc}
*/
protected function installModulesFromClassProperty(ContainerInterface $container) {
+ self::$modules = ['js_deprecation_log_test'];
if ($this->disableCssAnimations) {
- self::$modules = ['css_disable_transitions_test'];
+ self::$modules[] = 'css_disable_transitions_test';
}
parent::installModulesFromClassProperty($container);
}
@@ -108,6 +109,13 @@ protected function tearDown() {
// explaining what the problem is.
throw new \RuntimeException('Unfinished AJAX requests while tearing down a test');
}
+
+ $warnings = $this->getSession()->evaluateScript("JSON.parse(localStorage.getItem('js_deprecation_log_test.warnings') || JSON.stringify([]))");
+ foreach ($warnings as $warning) {
+ if (strpos($warning, '[Deprecation]') === 0) {
+ @trigger_error('Javascript Deprecation:' . substr($warning, 13), E_USER_DEPRECATED);
+ }
+ }
}
parent::tearDown();
}
diff --git a/core/tests/Drupal/Nightwatch/Assertions/deprecationErrorExists.js b/core/tests/Drupal/Nightwatch/Assertions/deprecationErrorExists.js
new file mode 100644
index 0000000000..7c15fe06e8
--- /dev/null
+++ b/core/tests/Drupal/Nightwatch/Assertions/deprecationErrorExists.js
@@ -0,0 +1,22 @@
+module.exports.assertion = function(expected) {
+ this.message = `Testing if "${expected}" deprecation error has been triggered`;
+ this.expected = expected;
+ this.pass = deprecationMessages => {
+ return deprecationMessages.includes(expected);
+ };
+ this.value = result => {
+ const localStorageEntries = JSON.parse(result.value);
+ const deprecationMessages = localStorageEntries !== null ? localStorageEntries.filter(message => {
+ return new RegExp('\[Deprecation\]').test(message);
+ }) : [];
+
+ return deprecationMessages.map(message => {
+ return message.replace('\[Deprecation\] ', '');
+ })
+ };
+ this.command = callback => {
+ return this.api.execute(function() {
+ return window.localStorage.getItem('js_deprecation_log_test.warnings');
+ }, callback);
+ };
+};
diff --git a/core/tests/Drupal/Nightwatch/Assertions/noDeprecationErrors.js b/core/tests/Drupal/Nightwatch/Assertions/noDeprecationErrors.js
new file mode 100644
index 0000000000..095da1434d
--- /dev/null
+++ b/core/tests/Drupal/Nightwatch/Assertions/noDeprecationErrors.js
@@ -0,0 +1,22 @@
+module.exports.assertion = function() {
+ this.message = 'Ensuring no deprecation errors have been triggered';
+ this.expected = '';
+ this.pass = deprecationMessages => {
+ return deprecationMessages.length === 0;
+ };
+ this.value = result => {
+ const localStorageEntries = JSON.parse(result.value);
+ const deprecationMessages = localStorageEntries !== null ? localStorageEntries.filter(message => {
+ return new RegExp('\[Deprecation\]').test(message);
+ }) : [];
+
+ return deprecationMessages.map(message => {
+ return message.replace('\[Deprecation\] ', '');
+ })
+ };
+ this.command = callback => {
+ return this.api.execute(function() {
+ return window.localStorage.getItem('js_deprecation_log_test.warnings');
+ }, callback);
+ };
+};
diff --git a/core/tests/Drupal/Nightwatch/Tests/exampleTest.js b/core/tests/Drupal/Nightwatch/Tests/exampleTest.js
index 04f21954d0..da58421350 100644
--- a/core/tests/Drupal/Nightwatch/Tests/exampleTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/exampleTest.js
@@ -22,6 +22,7 @@ module.exports = {
.drupalRelativeURL('/test-page')
.waitForElementVisible('@body', testPage.props.timeout)
.assert.containsText('@body', testPage.props.text)
+ .assert.noDeprecationErrors()
.drupalLogAndEnd({ onlyOnError: false });
},
};
diff --git a/core/tests/Drupal/Nightwatch/Tests/jsDeprecationTest.js b/core/tests/Drupal/Nightwatch/Tests/jsDeprecationTest.js
new file mode 100644
index 0000000000..bbae7134f2
--- /dev/null
+++ b/core/tests/Drupal/Nightwatch/Tests/jsDeprecationTest.js
@@ -0,0 +1,28 @@
+module.exports = {
+ '@tags': ['core'],
+ before(browser) {
+ browser.drupalInstall().drupalLoginAsAdmin(() => {
+ browser
+ .drupalRelativeURL('/admin/modules')
+ .setValue('input[type="search"]', 'JS Deprecation test')
+ .waitForElementVisible(
+ 'input[name="modules[js_deprecation_test][enable]"]',
+ 1000,
+ )
+ .click('input[name="modules[js_deprecation_test][enable]"]')
+ .click('input[type="submit"]'); // Submit module form.
+ });
+ },
+ after(browser) {
+ browser.drupalUninstall();
+ },
+ 'Test JavaScript deprecations': browser => {
+ browser
+ .drupalRelativeURL('/js_deprecation_test')
+ .waitForElementVisible('body', 1000)
+ .assert.containsText('h1.page-title', 'JsDeprecationTest')
+ .assert.deprecationErrorExists('This function is deprecated for testing purposes.')
+ .assert.deprecationErrorExists('This property is deprecated for testing purposes.')
+ .drupalLogAndEnd({ onlyOnError: false });
+ },
+};
diff --git a/core/tests/Drupal/Nightwatch/Tests/loginTest.js b/core/tests/Drupal/Nightwatch/Tests/loginTest.js
index 3f1bb0a971..5008e906c4 100644
--- a/core/tests/Drupal/Nightwatch/Tests/loginTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/loginTest.js
@@ -17,7 +17,8 @@ module.exports = {
})
.drupalLogin({ name: 'user', password: '123' })
.drupalRelativeURL('/admin/reports')
- .expect.element('h1.page-title')
- .text.to.contain('Reports');
+ .waitForElementVisible('body', 1000)
+ .assert.containsText('h1.page-title', 'Reports')
+ .assert.noDeprecationErrors();
},
};
diff --git a/core/tests/Drupal/Nightwatch/Tests/statesTest.js b/core/tests/Drupal/Nightwatch/Tests/statesTest.js
index f7ee1ba522..35bdd760a5 100644
--- a/core/tests/Drupal/Nightwatch/Tests/statesTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/statesTest.js
@@ -18,6 +18,7 @@ module.exports = {
browser
.drupalRelativeURL('/form-test/javascript-states-form')
.waitForElementVisible('body', 1000)
- .waitForElementNotVisible('input[name="textfield"]', 1000);
+ .waitForElementNotVisible('input[name="textfield"]', 1000)
+ .assert.noDeprecationErrors();
},
};