summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwebchick2012-10-28 21:40:07 (GMT)
committerwebchick2012-10-28 21:40:07 (GMT)
commit1943fb6167f4fa19f744a0de4e71a45361dcb568 (patch)
treeb7e44167003149956bc0af88d81357a19eec7a1b
parentfce50c0bd4a285c74e2b832d03851ea6d1a9d73f (diff)
Issue #1815602 by jessebeach, attiks, Jelle_S, gmclelland, Wim Leers: Introduce a polyfill for matchMedia.
-rw-r--r--core/misc/matchmedia.js156
-rw-r--r--core/modules/system/system.module10
2 files changed, 166 insertions, 0 deletions
diff --git a/core/misc/matchmedia.js b/core/misc/matchmedia.js
new file mode 100644
index 0000000..bd18fe8
--- /dev/null
+++ b/core/misc/matchmedia.js
@@ -0,0 +1,156 @@
+/**
+ * Polyfill the behavior of window.matchMedia.
+ *
+ * @see http://dev.w3.org/csswg/cssom-view/#widl-Window-matchMedia-MediaQueryList-DOMString-query
+ *
+ * Test whether a CSS media type or media query applies. Register listeners
+ * to MediaQueryList objects.
+ *
+ * Adapted from https://github.com/paulirish/matchMedia.js with the addition
+ * of addListener and removeListener. The polyfill referenced above uses
+ * polling to trigger registered listeners on matchMedia tests.
+ * This polyfill triggers tests on window resize and orientationchange.
+ */
+
+window.matchMedia = window.matchMedia || (function (doc, window) {
+
+ "use strict";
+
+ var docElem = doc.documentElement;
+ var refNode = docElem.firstElementChild || docElem.firstChild;
+ // fakeBody required for <FF4 when executed in <head>.
+ var fakeBody = doc.createElement("body");
+ var div = doc.createElement("div");
+
+ div.id = "mq-test-1";
+ div.style.cssText = "position:absolute;top:-100em";
+ fakeBody.style.background = "none";
+ fakeBody.appendChild(div);
+
+ /**
+ * A replacement for the native MediaQueryList object.
+ *
+ * @param {String} q
+ * A media query e.g. "screen" or "screen and (min-width: 28em)".
+ */
+ function MediaQueryList (q) {
+ this.media = q;
+ this.matches = false;
+ this.check.call(this);
+ }
+
+ /**
+ * Polyfill the addListener and removeListener methods.
+ */
+ MediaQueryList.prototype = {
+ listeners: [],
+
+ /**
+ * Perform the media query application check.
+ */
+ check: function () {
+ var isApplied;
+ div.innerHTML = "&shy;<style media=\"" + this.media + "\"> #mq-test-1 {width: 42px;}</style>";
+ docElem.insertBefore(fakeBody, refNode);
+ isApplied = div.offsetWidth === 42;
+ docElem.removeChild(fakeBody);
+ this.matches = isApplied;
+ },
+
+ /**
+ * Polyfill the addListener method of the MediaQueryList object.
+ *
+ * @param {Function} callback
+ * The callback to be invoked when the media query is applicable.
+ *
+ * @return {Object MediaQueryList}
+ * A MediaQueryList object that indicates whether the registered media
+ * query applies. The matches property is true when the media query
+ * applies and false when not. The original media query is referenced in
+ * the media property.
+ */
+ addListener: function (callback) {
+ var handler = (function (mql, debounced) {
+ return function () {
+ mql.check();
+ debounced.call(mql, mql);
+ };
+ }(this, debounce(callback, 250)));
+ this.listeners.push({
+ 'callback': callback,
+ 'handler': handler
+ });
+
+ // Associate the handler to the resize and orientationchange events.
+ if ('addEventListener' in window) {
+ window.addEventListener('resize', handler);
+ window.addEventListener('orientationchange', handler);
+ }
+ else if ('attachEvent' in window) {
+ window.attachEvent('onresize', handler);
+ window.attachEvent('onorientationchange', handler);
+ }
+ },
+
+ /**
+ * Polyfill the removeListener method of the MediaQueryList object.
+ *
+ * @param {Function} callback
+ * The callback to be removed from the set of listeners.
+ */
+ removeListener: function (callback) {
+ for (var i = 0, listeners = this.listeners; i < listeners.length; i++) {
+ if (listeners[i].callback === callback) {
+ // Disassociate the handler to the resize and orientationchange events.
+ if ('removeEventListener' in window) {
+ window.removeEventListener('resize', listeners[i].handler);
+ window.removeEventListener('orientationchange', listeners[i].handler);
+ }
+ else if ('detachEvent' in window) {
+ window.detachEvent('onresize', listeners[i].handler);
+ window.detachEvent('onorientationchange', listeners[i].handler);
+ }
+ listeners.splice(i, 1);
+ }
+ }
+ }
+ };
+
+ /**
+ * Limits the invocations of a function in a given time frame.
+ *
+ * @param {Function} callback
+ * The function to be invoked.
+ *
+ * @param {Number} wait
+ * The time period within which the callback function should only be
+ * invoked once. For example if the wait period is 250ms, then the callback
+ * will only be called at most 4 times per second.
+ */
+ function debounce (callback, wait) {
+ var timeout, result;
+ return function () {
+ var context = this;
+ var args = arguments;
+ var later = function () {
+ timeout = null;
+ result = callback.apply(context, args);
+ };
+ window.clearTimeout(timeout);
+ timeout = window.setTimeout(later, wait);
+ return result;
+ };
+ }
+
+ /**
+ * Return a MediaQueryList.
+ *
+ * @param {String} q
+ * A media query e.g. "screen" or "screen and (min-width: 28em)". The media
+ * query is checked for applicability before the object is returned.
+ */
+ return function (q) {
+ // Build a new MediaQueryList object with the result of the check.
+ return new MediaQueryList(q);
+ };
+}(document, window));
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 7f9f33a..98b0427 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1380,6 +1380,16 @@ function system_library_info() {
),
);
+ // matchMedia polyfill.
+ $libraries['matchmedia'] = array(
+ 'title' => 'window.matchMedia polyfill',
+ 'website' => 'http://drupal.org/node/1815602',
+ 'version' => VERSION,
+ 'js' => array(
+ 'core/misc/matchmedia.js' => array(),
+ ),
+ );
+
// Farbtastic.
$libraries['jquery.farbtastic'] = array(
'title' => 'Farbtastic',