From 9b67a7b7b7109f5ddf0451e09a4162fe7bf1a3cc Mon Sep 17 00:00:00 2001 From: Fabian Off Date: Fri, 1 Jul 2016 13:14:38 +0200 Subject: [PATCH] feature: resolve parameters in Page.url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows to build URLs for pages dynamically based on the `options.urlParams` that are handed over to the page. Uses the same node-module that express uses, so the very same syntax applies. For example use “http://httpbin.org/get?test=:test” with ```options = {urlParams: {test: true}}``` to get “http://httpbin.org/get?test=true”. --- README.md | 20 ++ dist/atv.js | 516 ++++++++++++++++++++++++++++++++++++++++++++++-- dist/atv.min.js | 10 +- package.json | 1 + src/page.js | 12 +- 5 files changed, 534 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 491b4ad..ddd2636 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Blazing fast Apple TV application development using pure JavaScript. - [Custom options while navigation](#custom-options-while-navigation) - [Creating Menu Page](#creating-menu-page) - [Application initialization using configuration](#application-initialization-using-configuration) + - [Dynamic Page URLs](#dynamic-page-urls) - [Sample Code](#sample-code) - [Useful Links](#useful-links) - [Contributions](#contributions) @@ -430,6 +431,25 @@ ATV.start({ }); ``` + +#### Dynamic Page URLs + +You can use express-style path-variables in `url`. The example below will resolve `:param` with `"value"`. +**Note that all defined parameters (i.e. every string in the `url` starting with a `:`) must be set in `urlParams`!** + +```javascript +ATV.Page.create({ + name: 'home', + url: 'path/to/your/api/that/returns/json?param=:param', + template: your_template_function +}); +``` +``` + +``` + ### Sample Code You can find a port of the original TVML Catalog sample code [re-written using atvjs](https://github.com/emadalam/tvml-catalog-using-atvjs). diff --git a/dist/atv.js b/dist/atv.js index 64b7297..5228c9b 100644 --- a/dist/atv.js +++ b/dist/atv.js @@ -86,19 +86,19 @@ return /******/ (function(modules) { // webpackBootstrap var _page2 = _interopRequireDefault(_page); - var _navigation = __webpack_require__(199); + var _navigation = __webpack_require__(201); var _navigation2 = _interopRequireDefault(_navigation); - var _handler = __webpack_require__(198); + var _handler = __webpack_require__(200); var _handler2 = _interopRequireDefault(_handler); - var _settings = __webpack_require__(201); + var _settings = __webpack_require__(203); var _settings2 = _interopRequireDefault(_settings); - var _menu = __webpack_require__(200); + var _menu = __webpack_require__(202); var _menu2 = _interopRequireDefault(_menu); @@ -5471,12 +5471,40 @@ return /******/ (function(modules) { // webpackBootstrap // shim for using process in browser var process = module.exports = {}; + + // cached from whatever global is present so that test runners that stub it + // don't break things. But we need to wrap it in a try catch in case it is + // wrapped in strict mode code which doesn't define any globals. It's inside a + // function because try/catches deoptimize in certain engines. + + var cachedSetTimeout; + var cachedClearTimeout; + + (function () { + try { + cachedSetTimeout = setTimeout; + } catch (e) { + cachedSetTimeout = function () { + throw new Error('setTimeout is not defined'); + } + } + try { + cachedClearTimeout = clearTimeout; + } catch (e) { + cachedClearTimeout = function () { + throw new Error('clearTimeout is not defined'); + } + } + } ()) var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); @@ -5492,7 +5520,7 @@ return /******/ (function(modules) { // webpackBootstrap if (draining) { return; } - var timeout = setTimeout(cleanUpNextTick); + var timeout = cachedSetTimeout(cleanUpNextTick); draining = true; var len = queue.length; @@ -5509,7 +5537,7 @@ return /******/ (function(modules) { // webpackBootstrap } currentQueue = null; draining = false; - clearTimeout(timeout); + cachedClearTimeout(timeout); } process.nextTick = function (fun) { @@ -5521,7 +5549,7 @@ return /******/ (function(modules) { // webpackBootstrap } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { - setTimeout(drainQueue, 0); + cachedSetTimeout(drainQueue, 0); } }; @@ -18905,6 +18933,10 @@ return /******/ (function(modules) { // webpackBootstrap var _lodash2 = _interopRequireDefault(_lodash); + var _pathToRegexp = __webpack_require__(198); + + var _pathToRegexp2 = _interopRequireDefault(_pathToRegexp); + var _parser = __webpack_require__(195); var _parser2 = _interopRequireDefault(_parser); @@ -18913,7 +18945,7 @@ return /******/ (function(modules) { // webpackBootstrap var _ajax2 = _interopRequireDefault(_ajax); - var _handler = __webpack_require__(198); + var _handler = __webpack_require__(200); var _handler2 = _interopRequireDefault(_handler); @@ -19063,6 +19095,13 @@ return /******/ (function(modules) { // webpackBootstrap * @return {function(options: Object): Promise} A function that returns promise upon execution */ function makePage(cfg) { + var url = cfg.url, + resolveURL; + // the URL contains path-variables, resolve them + if (typeof url === 'string' && url.indexOf(':') !== -1) { + // const parsedURL = url.parse(url); + resolveURL = _pathToRegexp2.default.compile(url); + } return function (options) { _lodash2.default.defaultsDeep(cfg, defaults); @@ -19079,9 +19118,9 @@ return /******/ (function(modules) { // webpackBootstrap cfg.ready(options, function (response) { return resolve(response || _lodash2.default.isUndefined(response) ? makeDom(cfg, response) : null); }, reject); - } else if (cfg.url) { + } else if (url) { // make ajax request if a url is provided - _ajax2.default.get(cfg.url, cfg.options).then(function (xhr) { + _ajax2.default.get(resolveURL ? resolveURL(options && options.urlParams || {}) : url, cfg.options).then(function (xhr) { resolve(makeDom(cfg, xhr.response)); }, function (xhr) { // if present, call the error handler @@ -19214,6 +19253,447 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, /* 198 */ +/***/ function(module, exports, __webpack_require__) { + + var isarray = __webpack_require__(199) + + /** + * Expose `pathToRegexp`. + */ + module.exports = pathToRegexp + module.exports.parse = parse + module.exports.compile = compile + module.exports.tokensToFunction = tokensToFunction + module.exports.tokensToRegExp = tokensToRegExp + + /** + * The main path matching regexp utility. + * + * @type {RegExp} + */ + var PATH_REGEXP = new RegExp([ + // Match escaped characters that would otherwise appear in future matches. + // This allows the user to escape special characters that won't transform. + '(\\\\.)', + // Match Express-style parameters and un-named parameters with a prefix + // and optional suffixes. Matches appear as: + // + // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] + // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] + // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] + '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' + ].join('|'), 'g') + + /** + * Parse a string for the raw tokens. + * + * @param {string} str + * @return {!Array} + */ + function parse (str) { + var tokens = [] + var key = 0 + var index = 0 + var path = '' + var res + + while ((res = PATH_REGEXP.exec(str)) != null) { + var m = res[0] + var escaped = res[1] + var offset = res.index + path += str.slice(index, offset) + index = offset + m.length + + // Ignore already escaped sequences. + if (escaped) { + path += escaped[1] + continue + } + + var next = str[index] + var prefix = res[2] + var name = res[3] + var capture = res[4] + var group = res[5] + var modifier = res[6] + var asterisk = res[7] + + // Push the current path onto the tokens. + if (path) { + tokens.push(path) + path = '' + } + + var partial = prefix != null && next != null && next !== prefix + var repeat = modifier === '+' || modifier === '*' + var optional = modifier === '?' || modifier === '*' + var delimiter = res[2] || '/' + var pattern = capture || group || (asterisk ? '.*' : '[^' + delimiter + ']+?') + + tokens.push({ + name: name || key++, + prefix: prefix || '', + delimiter: delimiter, + optional: optional, + repeat: repeat, + partial: partial, + asterisk: !!asterisk, + pattern: escapeGroup(pattern) + }) + } + + // Match any characters still remaining. + if (index < str.length) { + path += str.substr(index) + } + + // If the path exists, push it onto the end. + if (path) { + tokens.push(path) + } + + return tokens + } + + /** + * Compile a string to a template function for the path. + * + * @param {string} str + * @return {!function(Object=, Object=)} + */ + function compile (str) { + return tokensToFunction(parse(str)) + } + + /** + * Prettier encoding of URI path segments. + * + * @param {string} + * @return {string} + */ + function encodeURIComponentPretty (str) { + return encodeURI(str).replace(/[\/?#]/g, function (c) { + return '%' + c.charCodeAt(0).toString(16).toUpperCase() + }) + } + + /** + * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. + * + * @param {string} + * @return {string} + */ + function encodeAsterisk (str) { + return encodeURI(str).replace(/[?#]/g, function (c) { + return '%' + c.charCodeAt(0).toString(16).toUpperCase() + }) + } + + /** + * Expose a method for transforming tokens into the path function. + */ + function tokensToFunction (tokens) { + // Compile all the tokens into regexps. + var matches = new Array(tokens.length) + + // Compile all the patterns before compilation. + for (var i = 0; i < tokens.length; i++) { + if (typeof tokens[i] === 'object') { + matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$') + } + } + + return function (obj, opts) { + var path = '' + var data = obj || {} + var options = opts || {} + var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent + + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i] + + if (typeof token === 'string') { + path += token + + continue + } + + var value = data[token.name] + var segment + + if (value == null) { + if (token.optional) { + // Prepend partial segment prefixes. + if (token.partial) { + path += token.prefix + } + + continue + } else { + throw new TypeError('Expected "' + token.name + '" to be defined') + } + } + + if (isarray(value)) { + if (!token.repeat) { + throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`') + } + + if (value.length === 0) { + if (token.optional) { + continue + } else { + throw new TypeError('Expected "' + token.name + '" to not be empty') + } + } + + for (var j = 0; j < value.length; j++) { + segment = encode(value[j]) + + if (!matches[i].test(segment)) { + throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`') + } + + path += (j === 0 ? token.prefix : token.delimiter) + segment + } + + continue + } + + segment = token.asterisk ? encodeAsterisk(value) : encode(value) + + if (!matches[i].test(segment)) { + throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"') + } + + path += token.prefix + segment + } + + return path + } + } + + /** + * Escape a regular expression string. + * + * @param {string} str + * @return {string} + */ + function escapeString (str) { + return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1') + } + + /** + * Escape the capturing group by escaping special characters and meaning. + * + * @param {string} group + * @return {string} + */ + function escapeGroup (group) { + return group.replace(/([=!:$\/()])/g, '\\$1') + } + + /** + * Attach the keys as a property of the regexp. + * + * @param {!RegExp} re + * @param {Array} keys + * @return {!RegExp} + */ + function attachKeys (re, keys) { + re.keys = keys + return re + } + + /** + * Get the flags for a regexp from the options. + * + * @param {Object} options + * @return {string} + */ + function flags (options) { + return options.sensitive ? '' : 'i' + } + + /** + * Pull out keys from a regexp. + * + * @param {!RegExp} path + * @param {!Array} keys + * @return {!RegExp} + */ + function regexpToRegexp (path, keys) { + // Use a negative lookahead to match only capturing groups. + var groups = path.source.match(/\((?!\?)/g) + + if (groups) { + for (var i = 0; i < groups.length; i++) { + keys.push({ + name: i, + prefix: null, + delimiter: null, + optional: false, + repeat: false, + partial: false, + asterisk: false, + pattern: null + }) + } + } + + return attachKeys(path, keys) + } + + /** + * Transform an array into a regexp. + * + * @param {!Array} path + * @param {Array} keys + * @param {!Object} options + * @return {!RegExp} + */ + function arrayToRegexp (path, keys, options) { + var parts = [] + + for (var i = 0; i < path.length; i++) { + parts.push(pathToRegexp(path[i], keys, options).source) + } + + var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options)) + + return attachKeys(regexp, keys) + } + + /** + * Create a path regexp from string input. + * + * @param {string} path + * @param {!Array} keys + * @param {!Object} options + * @return {!RegExp} + */ + function stringToRegexp (path, keys, options) { + var tokens = parse(path) + var re = tokensToRegExp(tokens, options) + + // Attach keys back to the regexp. + for (var i = 0; i < tokens.length; i++) { + if (typeof tokens[i] !== 'string') { + keys.push(tokens[i]) + } + } + + return attachKeys(re, keys) + } + + /** + * Expose a function for taking tokens and returning a RegExp. + * + * @param {!Array} tokens + * @param {Object=} options + * @return {!RegExp} + */ + function tokensToRegExp (tokens, options) { + options = options || {} + + var strict = options.strict + var end = options.end !== false + var route = '' + var lastToken = tokens[tokens.length - 1] + var endsWithSlash = typeof lastToken === 'string' && /\/$/.test(lastToken) + + // Iterate over the tokens and create our regexp string. + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i] + + if (typeof token === 'string') { + route += escapeString(token) + } else { + var prefix = escapeString(token.prefix) + var capture = '(?:' + token.pattern + ')' + + if (token.repeat) { + capture += '(?:' + prefix + capture + ')*' + } + + if (token.optional) { + if (!token.partial) { + capture = '(?:' + prefix + '(' + capture + '))?' + } else { + capture = prefix + '(' + capture + ')?' + } + } else { + capture = prefix + '(' + capture + ')' + } + + route += capture + } + } + + // In non-strict mode we allow a slash at the end of match. If the path to + // match already ends with a slash, we remove it for consistency. The slash + // is valid at the end of a path match, not in the middle. This is important + // in non-ending mode, where "/test/" shouldn't match "/test//route". + if (!strict) { + route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?' + } + + if (end) { + route += '$' + } else { + // In non-ending mode, we need the capturing groups to match as much as + // possible by using a positive lookahead to the end or next path segment. + route += strict && endsWithSlash ? '' : '(?=\\/|$)' + } + + return new RegExp('^' + route, flags(options)) + } + + /** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + * + * @param {(string|RegExp|Array)} path + * @param {(Array|Object)=} keys + * @param {Object=} options + * @return {!RegExp} + */ + function pathToRegexp (path, keys, options) { + keys = keys || [] + + if (!isarray(keys)) { + options = /** @type {!Object} */ (keys) + keys = [] + } else if (!options) { + options = {} + } + + if (path instanceof RegExp) { + return regexpToRegexp(path, /** @type {!Array} */ (keys)) + } + + if (isarray(path)) { + return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options) + } + + return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options) + } + + +/***/ }, +/* 199 */ +/***/ function(module, exports) { + + module.exports = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; + }; + + +/***/ }, +/* 200 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -19224,11 +19704,11 @@ return /******/ (function(modules) { // webpackBootstrap var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); - var _navigation = __webpack_require__(199); + var _navigation = __webpack_require__(201); var _navigation2 = _interopRequireDefault(_navigation); - var _menu = __webpack_require__(200); + var _menu = __webpack_require__(202); var _menu2 = _interopRequireDefault(_menu); @@ -19425,8 +19905,8 @@ return /******/ (function(modules) { // webpackBootstrap return doc.querySelectorAll(selector); }); // catch any errors while document selection } else { - elements = [doc]; - } + elements = [doc]; + } elements = _.isError(elements) ? [] : elements; _.each(fns, function (fn) { fn = _.isString(fn) ? cfg[fn] : fn; // assume the function to be present on the page configuration obeject @@ -19582,7 +20062,7 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 199 */ +/* 201 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -19603,7 +20083,7 @@ return /******/ (function(modules) { // webpackBootstrap var _parser2 = _interopRequireDefault(_parser); - var _menu = __webpack_require__(200); + var _menu = __webpack_require__(202); var _menu2 = _interopRequireDefault(_menu); @@ -20124,7 +20604,7 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 200 */ +/* 202 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; @@ -20316,7 +20796,7 @@ return /******/ (function(modules) { // webpackBootstrap }; /***/ }, -/* 201 */ +/* 203 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; diff --git a/dist/atv.min.js b/dist/atv.min.js index 6bc7652..5fe4ba8 100644 --- a/dist/atv.min.js +++ b/dist/atv.min.js @@ -2,8 +2,8 @@ * Copyright (c) Emad Alam http://emad.in * https://github.com/emadalam/atvjs */ -!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.ATV=n():t.ATV=n()}(this,function(){return function(t){function n(e){if(r[e])return r[e].exports;var o=r[e]={exports:{},id:e,loaded:!1};return t[e].call(o.exports,o,o.exports,n),o.loaded=!0,o.exports}var r={};return n.m=t,n.c=r,n.p="",n(0)}([function(t,n,r){"use strict";function e(t){return t&&t.__esModule?t:{"default":t}}function o(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];f["default"].each(M,function(n,r){var e=k[r],o={};f["default"].each(n,function(n){return o[n]=t[n]}),e.setOptions&&e.setOptions(o)})}function i(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];f["default"].each(D,function(n,r){return App[r]=f["default"].partial(n,f["default"],f["default"].isFunction(t[r])?t[r]:f["default"].noop)})}function u(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];P||(o(t),i(t),t.bootloaded&&App.onLaunch(App.launchOptions),P=!0)}function a(t,n){App.onReload(t),App.reload(t,n)}r(1);var c=r(191),f=e(c),s=r(193),l=e(s),p=r(194),h=e(p),v=r(195),g=e(v),d=r(196),y=e(d),m=r(197),_=e(m),b=r(199),w=e(b),x=r(198),S=e(x),O=r(201),j=e(O),A=r(200),E=e(A),M={Ajax:[],Parser:[],Page:["style"],Navigation:["menu","templates"],Handler:["handlers"]},P=!1,k={_:f["default"],LZString:h["default"],Ajax:y["default"],Navigation:w["default"],Page:_["default"],Parser:g["default"],Handler:S["default"],Settings:j["default"],Menu:E["default"]},D={onLaunch:function(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=arguments[1];k.launchOptions=t,n(t)},onError:function(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=arguments[1];n(t)},onResume:function(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=arguments[1];n(t)},onSuspend:function(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=arguments[1];n(t)},onExit:function(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=arguments[1];n(t)},onReload:function(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],n=arguments[1];n(t)}};f["default"].assign(k,l["default"],{start:u,reload:a}),t.exports=k},function(t,n,r){(function(t){"use strict";if(r(2),r(189),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed");t._babelPolyfill=!0}).call(n,function(){return this}())},function(t,n,r){r(3),r(36),r(42),r(44),r(46),r(48),r(50),r(52),r(53),r(54),r(55),r(56),r(57),r(58),r(59),r(60),r(61),r(62),r(63),r(66),r(67),r(68),r(70),r(71),r(72),r(73),r(74),r(75),r(76),r(78),r(79),r(80),r(82),r(83),r(84),r(86),r(87),r(88),r(89),r(90),r(91),r(92),r(93),r(94),r(95),r(96),r(97),r(98),r(99),r(104),r(105),r(109),r(110),r(112),r(113),r(118),r(119),r(122),r(124),r(126),r(128),r(129),r(130),r(132),r(133),r(135),r(136),r(137),r(138),r(145),r(148),r(149),r(151),r(152),r(153),r(154),r(155),r(156),r(157),r(158),r(159),r(160),r(161),r(162),r(164),r(165),r(166),r(167),r(168),r(169),r(171),r(172),r(173),r(174),r(176),r(177),r(179),r(180),r(182),r(183),r(184),r(187),r(188),t.exports=r(7)},function(t,n,r){"use strict";var e,o=r(4),i=r(5),u=r(10),a=r(9),c=r(16),f=r(17),s=r(19),l=r(20),p=r(21),h=r(11),v=r(22),g=r(15),d=r(18),y=r(23),m=r(25),_=r(27),b=r(28),w=r(29),x=r(26),S=r(13)("__proto__"),O=r(30),j=r(35)(!1),A=Object.prototype,E=Array.prototype,M=E.slice,P=E.join,k=o.setDesc,D=o.getDesc,F=o.setDescs,I={};u||(e=!h(function(){return 7!=k(f("div"),"a",{get:function(){return 7}}).a}),o.setDesc=function(t,n,r){if(e)try{return k(t,n,r)}catch(o){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(v(t)[n]=r.value),t},o.getDesc=function(t,n){if(e)try{return D(t,n)}catch(r){}return s(t,n)?a(!A.propertyIsEnumerable.call(t,n),t[n]):void 0},o.setDescs=F=function(t,n){v(t);for(var r,e=o.getKeys(n),i=e.length,u=0;i>u;)o.setDesc(t,r=e[u++],n[r]);return t}),i(i.S+i.F*!u,"Object",{getOwnPropertyDescriptor:o.getDesc,defineProperty:o.setDesc,defineProperties:F});var N="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(","),T=N.concat("length","prototype"),R=N.length,L=function(){var t,n=f("iframe"),r=R,e=">";for(n.style.display="none",c.appendChild(n),n.src="javascript:",t=n.contentWindow.document,t.open(),t.write("