PageRenderTime 124ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 1ms

/www/facebook-js-sdk.js

https://github.com/rock14/FacebookConnect
JavaScript | 14638 lines | 11090 code | 516 blank | 3032 comment | 565 complexity | 6daef80396bef2ddb4d70b20ea886c4d MD5 | raw file
  1. /*1329323125,171364642,JIT Construction: v510186,en_US*/
  2. /**
  3. * Copyright Facebook Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. *
  18. *
  19. * @provides fb.prelude
  20. */
  21. /**
  22. * Prelude.
  23. *
  24. * Namespaces are one honking great idea -- let's do more of those!
  25. * -- Tim Peters
  26. *
  27. * The Prelude is what keeps us from being messy. In order to co-exist with
  28. * arbitary environments, we need to control our footprint. The one and only
  29. * rule to follow here is that we need to limit the globals we introduce. The
  30. * only global we should every have is ``FB``. This is exactly what the prelude
  31. * enables us to do.
  32. *
  33. * The main method to take away from this file is `FB.copy()`_. As the name
  34. * suggests it copies things. Its powerful -- but to get started you only need
  35. * to know that this is what you use when you are augmenting the FB object. For
  36. * example, this is skeleton for how ``FB.Event`` is defined::
  37. *
  38. * FB.provide('Event', {
  39. * subscribe: function() { ... },
  40. * unsubscribe: function() { ... },
  41. * fire: function() { ... }
  42. * });
  43. *
  44. * This is similar to saying::
  45. *
  46. * FB.Event = {
  47. * subscribe: function() { ... },
  48. * unsubscribe: function() { ... },
  49. * fire: function() { ... }
  50. * };
  51. *
  52. * Except it does some housekeeping, prevents redefinition by default and other
  53. * goodness.
  54. *
  55. * .. _FB.copy(): #method_FB.copy
  56. *
  57. * @class FB
  58. * @static
  59. * @access private
  60. */
  61. if (!window.FB) {
  62. window.FB = {
  63. // use the init method to set these values correctly
  64. _apiKey : null,
  65. _authResponse : null,
  66. _userStatus : 'unknown', // or 'notConnected' or 'connected'
  67. // logging is enabled by default. this is the logging shown to the
  68. // developer and not at all noisy.
  69. _logging: true,
  70. _inCanvas: (
  71. (window.name.indexOf('iframe_canvas') > -1) ||
  72. (window.name.indexOf('app_runner') > -1)),
  73. // Determines if we should use HTTPS when attempting cross-domain
  74. // communication with facebook.com. This is assumed to be the case when
  75. // window.name contains "_fb_https". This value may also be set by the
  76. // response from FB.login() or FB.getLoginStatus()
  77. _https: (window.name.indexOf('_fb_https') > -1),
  78. //
  79. // DYNAMIC DATA
  80. //
  81. // the various domains needed for using Connect
  82. _domain: {
  83. api : 'https://api.facebook.com/',
  84. api_read : 'https://api-read.facebook.com/',
  85. cdn : 'http://static.ak.fbcdn.net/',
  86. https_cdn : 'https://s-static.ak.fbcdn.net/',
  87. graph : 'https://graph.facebook.com/',
  88. staticfb : 'http://static.ak.facebook.com/',
  89. https_staticfb : 'https://s-static.ak.facebook.com/',
  90. www : 'http://www.facebook.com/',
  91. https_www : 'https://www.facebook.com/',
  92. m : 'http://m.facebook.com/',
  93. https_m : 'https://m.facebook.com/'
  94. },
  95. _locale: null,
  96. _localeIsRtl: false,
  97. // CORDOVA PATCH
  98. _nativeInterface : null,
  99. /**
  100. * Retrieve one of the various domains needed for Connect.
  101. *
  102. * @access private
  103. * @param domain (String) The domain to retrieve
  104. * @param noForcedHTTPS (bool) Do not force https domain
  105. */
  106. getDomain: function(domain, noForcedHTTPS) {
  107. var forceHTTPS = !noForcedHTTPS &&
  108. (window.location.protocol == 'https:' || FB._https);
  109. switch (domain) {
  110. case 'api':
  111. return FB._domain.api;
  112. case 'api_read':
  113. return FB._domain.api_read;
  114. case 'cdn':
  115. return forceHTTPS ? FB._domain.https_cdn : FB._domain.cdn;
  116. case 'cdn_foreign':
  117. return FB._domain.cdn_foreign;
  118. case 'https_cdn':
  119. return FB._domain.https_cdn;
  120. case 'graph':
  121. return FB._domain.graph;
  122. case 'staticfb':
  123. return forceHTTPS ? FB._domain.https_staticfb : FB._domain.staticfb;
  124. case 'https_staticfb':
  125. return FB._domain.https_staticfb;
  126. case 'www':
  127. return forceHTTPS ? FB._domain.https_www : FB._domain.www;
  128. case 'https_www':
  129. return FB._domain.https_www;
  130. case 'm':
  131. return forceHTTPS ? FB._domain.https_m : FB._domain.m;
  132. case 'https_m':
  133. return FB._domain.https_m;
  134. }
  135. },
  136. /**
  137. * Copies things from source into target.
  138. *
  139. * @access private
  140. * @param target {Object} the target object where things will be copied
  141. * into
  142. * @param source {Object} the source object where things will be copied
  143. * from
  144. * @param overwrite {Boolean} indicate if existing items should be
  145. * overwritten
  146. * @param transform {function} [Optional], transformation function for
  147. * each item
  148. */
  149. copy: function(target, source, overwrite, transform) {
  150. for (var key in source) {
  151. if (overwrite || typeof target[key] === 'undefined') {
  152. target[key] = transform ? transform(source[key]) : source[key];
  153. }
  154. }
  155. return target;
  156. },
  157. /**
  158. * Create a namespaced object.
  159. *
  160. * @access private
  161. * @param name {String} full qualified name ('Util.foo', etc.)
  162. * @param value {Object} value to set. Default value is {}. [Optional]
  163. * @return {Object} The created object
  164. */
  165. create: function(name, value) {
  166. var node = window.FB, // We will use 'FB' as root namespace
  167. nameParts = name ? name.split('.') : [],
  168. c = nameParts.length;
  169. for (var i = 0; i < c; i++) {
  170. var part = nameParts[i];
  171. var nso = node[part];
  172. if (!nso) {
  173. nso = (value && i + 1 == c) ? value : {};
  174. node[part] = nso;
  175. }
  176. node = nso;
  177. }
  178. return node;
  179. },
  180. /**
  181. * Copy stuff from one object to the specified namespace that
  182. * is FB.<target>.
  183. * If the namespace target doesn't exist, it will be created automatically.
  184. *
  185. * @access private
  186. * @param target {Object|String} the target object to copy into
  187. * @param source {Object} the source object to copy from
  188. * @param overwrite {Boolean} indicate if we should overwrite
  189. * @return {Object} the *same* target object back
  190. */
  191. provide: function(target, source, overwrite) {
  192. // a string means a dot separated object that gets appended to, or created
  193. return FB.copy(
  194. typeof target == 'string' ? FB.create(target) : target,
  195. source,
  196. overwrite
  197. );
  198. },
  199. /**
  200. * Generates a weak random ID.
  201. *
  202. * @access private
  203. * @return {String} a random ID
  204. */
  205. guid: function() {
  206. return 'f' + (Math.random() * (1<<30)).toString(16).replace('.', '');
  207. },
  208. /**
  209. * Logs a message for the developer if logging is on.
  210. *
  211. * @access private
  212. * @param args {Object} the thing to log
  213. */
  214. log: function(args) {
  215. if (FB._logging) {
  216. //TODO what is window.Debug, and should it instead be relying on the
  217. // event fired below?
  218. //#JSCOVERAGE_IF 0
  219. if (window.Debug && window.Debug.writeln) {
  220. window.Debug.writeln(args);
  221. } else if (window.console) {
  222. window.console.log(args);
  223. }
  224. //#JSCOVERAGE_ENDIF
  225. }
  226. // fire an event if the event system is available
  227. if (FB.Event) {
  228. FB.Event.fire('fb.log', args);
  229. }
  230. },
  231. /**
  232. * Shortcut for document.getElementById
  233. * @method $
  234. * @param {string} DOM id
  235. * @return DOMElement
  236. * @access private
  237. */
  238. $: function(id) {
  239. return document.getElementById(id);
  240. }
  241. };
  242. }
  243. /**
  244. * Copyright Facebook Inc.
  245. *
  246. * Licensed under the Apache License, Version 2.0 (the "License");
  247. * you may not use this file except in compliance with the License.
  248. * You may obtain a copy of the License at
  249. *
  250. * http://www.apache.org/licenses/LICENSE-2.0
  251. *
  252. * Unless required by applicable law or agreed to in writing, software
  253. * distributed under the License is distributed on an "AS IS" BASIS,
  254. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  255. * See the License for the specific language governing permissions and
  256. * limitations under the License.
  257. *
  258. * @provides fb.array
  259. * @layer basic
  260. * @requires fb.prelude
  261. */
  262. /**
  263. * Array related helper methods.
  264. *
  265. * @class FB.Array
  266. * @private
  267. * @static
  268. */
  269. FB.provide('Array', {
  270. /**
  271. * Get index of item inside an array. Return's -1 if element is not found.
  272. *
  273. * @param arr {Array} Array to look through.
  274. * @param item {Object} Item to locate.
  275. * @return {Number} Index of item.
  276. */
  277. indexOf: function (arr, item) {
  278. if (arr.indexOf) {
  279. return arr.indexOf(item);
  280. }
  281. var length = arr.length;
  282. if (length) {
  283. for (var index = 0; index < length; index++) {
  284. if (arr[index] === item) {
  285. return index;
  286. }
  287. }
  288. }
  289. return -1;
  290. },
  291. /**
  292. * Merge items from source into target, but only if they dont exist. Returns
  293. * the target array back.
  294. *
  295. * @param target {Array} Target array.
  296. * @param source {Array} Source array.
  297. * @return {Array} Merged array.
  298. */
  299. merge: function(target, source) {
  300. for (var i=0; i < source.length; i++) {
  301. if (FB.Array.indexOf(target, source[i]) < 0) {
  302. target.push(source[i]);
  303. }
  304. }
  305. return target;
  306. },
  307. /**
  308. * Create an new array from the given array and a filter function.
  309. *
  310. * @param arr {Array} Source array.
  311. * @param fn {Function} Filter callback function.
  312. * @return {Array} Filtered array.
  313. */
  314. filter: function(arr, fn) {
  315. var b = [];
  316. for (var i=0; i < arr.length; i++) {
  317. if (fn(arr[i])) {
  318. b.push(arr[i]);
  319. }
  320. }
  321. return b;
  322. },
  323. /**
  324. * Create an array from the keys in an object.
  325. *
  326. * Example: keys({'x': 2, 'y': 3'}) returns ['x', 'y']
  327. *
  328. * @param obj {Object} Source object.
  329. * @param proto {Boolean} Specify true to include inherited properties.
  330. * @return {Array} The array of keys.
  331. */
  332. keys: function(obj, proto) {
  333. var arr = [];
  334. for (var key in obj) {
  335. if (proto || obj.hasOwnProperty(key)) {
  336. arr.push(key);
  337. }
  338. }
  339. return arr;
  340. },
  341. /**
  342. * Create an array by performing transformation on the items in a source
  343. * array.
  344. *
  345. * @param arr {Array} Source array.
  346. * @param transform {Function} Transformation function.
  347. * @return {Array} The transformed array.
  348. */
  349. map: function(arr, transform) {
  350. var ret = [];
  351. for (var i=0; i < arr.length; i++) {
  352. ret.push(transform(arr[i]));
  353. }
  354. return ret;
  355. },
  356. /**
  357. * For looping through Arrays and Objects.
  358. *
  359. * @param {Object} item an Array or an Object
  360. * @param {Function} fn the callback function for iteration.
  361. * The function will be pass (value, [index/key], item) parameters
  362. * @param {Bool} proto indicate if properties from the prototype should
  363. * be included
  364. *
  365. */
  366. forEach: function(item, fn, proto) {
  367. if (!item) {
  368. return;
  369. }
  370. if (Object.prototype.toString.apply(item) === '[object Array]' ||
  371. (!(item instanceof Function) && typeof item.length == 'number')) {
  372. if (item.forEach) {
  373. item.forEach(fn);
  374. } else {
  375. for (var i=0, l=item.length; i<l; i++) {
  376. fn(item[i], i, item);
  377. }
  378. }
  379. } else {
  380. for (var key in item) {
  381. if (proto || item.hasOwnProperty(key)) {
  382. fn(item[key], key, item);
  383. }
  384. }
  385. }
  386. },
  387. /**
  388. * Turns HTMLCollections or anything array-like (that has a `length`)
  389. * such as function `arguments` into a real array
  390. *
  391. * @param {HTMLCollection} coll Array-like collection
  392. * @return {Array}
  393. */
  394. toArray: function(coll) {
  395. for (var i = 0, a = [], len = coll.length; i < len; i++) {
  396. a[i] = coll[i];
  397. }
  398. return a;
  399. }
  400. });
  401. /**
  402. * Copyright Facebook Inc.
  403. *
  404. * Licensed under the Apache License, Version 2.0 (the "License");
  405. * you may not use this file except in compliance with the License.
  406. * You may obtain a copy of the License at
  407. *
  408. * http://www.apache.org/licenses/LICENSE-2.0
  409. *
  410. * Unless required by applicable law or agreed to in writing, software
  411. * distributed under the License is distributed on an "AS IS" BASIS,
  412. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  413. * See the License for the specific language governing permissions and
  414. * limitations under the License.
  415. *
  416. *
  417. *
  418. * @provides fb.qs
  419. * @requires fb.prelude fb.array
  420. */
  421. /**
  422. * Query String encoding & decoding.
  423. *
  424. * @class FB.QS
  425. * @static
  426. * @access private
  427. */
  428. FB.provide('QS', {
  429. /**
  430. * Encode parameters to a query string.
  431. *
  432. * @access private
  433. * @param params {Object} the parameters to encode
  434. * @param sep {String} the separator string (defaults to '&')
  435. * @param encode {Boolean} indicate if the key/value should be URI encoded
  436. * @return {String} the query string
  437. */
  438. encode: function(params, sep, encode) {
  439. sep = sep === undefined ? '&' : sep;
  440. encode = encode === false ? function(s) { return s; } : encodeURIComponent;
  441. var pairs = [];
  442. FB.Array.forEach(params, function(val, key) {
  443. if (val !== null && typeof val != 'undefined') {
  444. pairs.push(encode(key) + '=' + encode(val));
  445. }
  446. });
  447. pairs.sort();
  448. return pairs.join(sep);
  449. },
  450. /**
  451. * Decode a query string into a parameters object.
  452. *
  453. * @access private
  454. * @param str {String} the query string
  455. * @return {Object} the parameters to encode
  456. */
  457. decode: function(str) {
  458. var
  459. decode = decodeURIComponent,
  460. params = {},
  461. parts = str.split('&'),
  462. i,
  463. pair;
  464. for (i=0; i<parts.length; i++) {
  465. pair = parts[i].split('=', 2);
  466. if (pair && pair[0]) {
  467. params[decode(pair[0])] = decode(pair[1] || '');
  468. }
  469. }
  470. return params;
  471. }
  472. });
  473. /**
  474. * Copyright Facebook Inc.
  475. *
  476. * Licensed under the Apache License, Version 2.0 (the "License");
  477. * you may not use this file except in compliance with the License.
  478. * You may obtain a copy of the License at
  479. *
  480. * http://www.apache.org/licenses/LICENSE-2.0
  481. *
  482. * Unless required by applicable law or agreed to in writing, software
  483. * distributed under the License is distributed on an "AS IS" BASIS,
  484. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  485. * See the License for the specific language governing permissions and
  486. * limitations under the License.
  487. *
  488. *
  489. *
  490. * @provides fb.content
  491. * @requires fb.prelude fb.array
  492. */
  493. /**
  494. * "Content" is a very flexible term. Helpers for things like hidden
  495. * DOM content, iframes and popups.
  496. *
  497. * @class FB.Content
  498. * @static
  499. * @access private
  500. */
  501. FB.provide('Content', {
  502. _root : null,
  503. _hiddenRoot : null,
  504. _callbacks : {},
  505. /**
  506. * Append some content.
  507. *
  508. * @access private
  509. * @param content {String|Node} a DOM Node or HTML string
  510. * @param root {Node} (optional) a custom root node
  511. * @return {Node} the node that was just appended
  512. */
  513. append: function(content, root) {
  514. // setup the root node
  515. if (!root) {
  516. if (!FB.Content._root) {
  517. FB.Content._root = root = FB.$('fb-root');
  518. if (!root) {
  519. FB.log('The "fb-root" div has not been created.');
  520. return;
  521. } else {
  522. root.className += ' fb_reset';
  523. }
  524. } else {
  525. root = FB.Content._root;
  526. }
  527. }
  528. if (typeof content == 'string') {
  529. var div = document.createElement('div');
  530. root.appendChild(div).innerHTML = content;
  531. return div;
  532. } else {
  533. return root.appendChild(content);
  534. }
  535. },
  536. /**
  537. * Append some hidden content.
  538. *
  539. * @access private
  540. * @param content {String|Node} a DOM Node or HTML string
  541. * @return {Node} the node that was just appended
  542. */
  543. appendHidden: function(content) {
  544. if (!FB.Content._hiddenRoot) {
  545. var
  546. hiddenRoot = document.createElement('div'),
  547. style = hiddenRoot.style;
  548. style.position = 'absolute';
  549. style.top = '-10000px';
  550. style.width = style.height = 0;
  551. FB.Content._hiddenRoot = FB.Content.append(hiddenRoot);
  552. }
  553. return FB.Content.append(content, FB.Content._hiddenRoot);
  554. },
  555. /**
  556. * Insert a new iframe. Unfortunately, its tricker than you imagine.
  557. *
  558. * NOTE: These iframes have no border, overflow hidden and no scrollbars.
  559. *
  560. * The opts can contain:
  561. * root DOMElement required root node (must be empty)
  562. * url String required iframe src attribute
  563. * className String optional class attribute
  564. * height Integer optional height in px
  565. * id String optional id attribute
  566. * name String optional name attribute
  567. * onInsert Function optional callback directly after insertion
  568. * onload Function optional onload handler
  569. * width Integer optional width in px
  570. *
  571. * @access private
  572. * @param opts {Object} the options described above
  573. */
  574. insertIframe: function(opts) {
  575. //
  576. // Browsers evolved. Evolution is messy.
  577. //
  578. opts.id = opts.id || FB.guid();
  579. opts.name = opts.name || FB.guid();
  580. // Dear IE, screw you. Only works with the magical incantations.
  581. // Dear FF, screw you too. Needs src _after_ DOM insertion.
  582. // Dear Webkit, you're okay. Works either way.
  583. var
  584. guid = FB.guid(),
  585. // Since we set the src _after_ inserting the iframe node into the DOM,
  586. // some browsers will fire two onload events, once for the first empty
  587. // iframe insertion and then again when we set the src. Here some
  588. // browsers are Webkit browsers which seem to be trying to do the
  589. // "right thing". So we toggle this boolean right before we expect the
  590. // correct onload handler to get fired.
  591. srcSet = false,
  592. onloadDone = false;
  593. FB.Content._callbacks[guid] = function() {
  594. if (srcSet && !onloadDone) {
  595. onloadDone = true;
  596. opts.onload && opts.onload(opts.root.firstChild);
  597. }
  598. };
  599. //#JSCOVERAGE_IF
  600. if (document.attachEvent) {
  601. // Initial src is set to javascript:false so as to not trigger the
  602. // unsecure content warning.
  603. var html = (
  604. '<iframe' +
  605. ' id="' + opts.id + '"' +
  606. ' name="' + opts.name + '"' +
  607. (opts.title ? ' title="' + opts.title + '"' : '') +
  608. (opts.className ? ' class="' + opts.className + '"' : '') +
  609. ' style="border:none;' +
  610. (opts.width ? 'width:' + opts.width + 'px;' : '') +
  611. (opts.height ? 'height:' + opts.height + 'px;' : '') +
  612. '"' +
  613. ' src="javascript:false;"' +
  614. ' frameborder="0"' +
  615. ' scrolling="no"' +
  616. ' allowtransparency="true"' +
  617. ' onload="FB.Content._callbacks.' + guid + '()"' +
  618. '></iframe>'
  619. );
  620. // There is an IE bug with iframe caching that we have to work around. We
  621. // need to load a dummy iframe to consume the initial cache stream. The
  622. // setTimeout actually sets the content to the HTML we created above, and
  623. // because its the second load, we no longer suffer from cache sickness.
  624. // It must be javascript:false instead of about:blank, otherwise IE6 will
  625. // complain in https.
  626. // Since javascript:false actually result in an iframe containing the
  627. // string 'false', we set the iframe height to 1px so that it gets loaded
  628. // but stays invisible.
  629. opts.root.innerHTML = '<iframe src="javascript:false"'+
  630. ' frameborder="0"'+
  631. ' scrolling="no"'+
  632. ' style="height:1px"></iframe>';
  633. // Now we'll be setting the real src.
  634. srcSet = true;
  635. // You may wonder why this is a setTimeout. Read the IE source if you can
  636. // somehow get your hands on it, and tell me if you figure it out. This
  637. // is a continuation of the above trick which apparently does not work if
  638. // the innerHTML is changed right away. We need to break apart the two
  639. // with this setTimeout 0 which seems to fix the issue.
  640. window.setTimeout(function() {
  641. opts.root.innerHTML = html;
  642. opts.root.firstChild.src = opts.url;
  643. opts.onInsert && opts.onInsert(opts.root.firstChild);
  644. }, 0);
  645. } else {
  646. // This block works for all non IE browsers. But it's specifically
  647. // designed for FF where we need to set the src after inserting the
  648. // iframe node into the DOM to prevent cache issues.
  649. var node = document.createElement('iframe');
  650. node.id = opts.id;
  651. node.name = opts.name;
  652. node.onload = FB.Content._callbacks[guid];
  653. node.scrolling = 'no';
  654. node.style.border = 'none';
  655. node.style.overflow = 'hidden';
  656. if (opts.title) {
  657. node.title = opts.title;
  658. }
  659. if (opts.className) {
  660. node.className = opts.className;
  661. }
  662. if (opts.height) {
  663. node.style.height = opts.height + 'px';
  664. }
  665. if (opts.width) {
  666. if (opts.width == '100%') {
  667. node.style.width = opts.width;
  668. } else {
  669. node.style.width = opts.width + 'px';
  670. }
  671. }
  672. opts.root.appendChild(node);
  673. // Now we'll be setting the real src.
  674. srcSet = true;
  675. node.src = opts.url;
  676. opts.onInsert && opts.onInsert(node);
  677. }
  678. },
  679. /**
  680. * Dynamically generate a <form> and submits it to the given target.
  681. * Uses POST by default.
  682. *
  683. * The opts MUST contain:
  684. * url String action URL for the form
  685. * target String the target for the form
  686. * params Object the key/values to be used as POST input
  687. *
  688. * @access protected
  689. * @param opts {Object} the options
  690. * @param get Should we use get instead?
  691. */
  692. submitToTarget: function(opts, get) {
  693. var form = document.createElement('form');
  694. form.action = opts.url;
  695. form.target = opts.target;
  696. form.method = (get) ? 'GET' : 'POST';
  697. FB.Content.appendHidden(form);
  698. FB.Array.forEach(opts.params, function(val, key) {
  699. if (val !== null && val !== undefined) {
  700. var input = document.createElement('input');
  701. input.name = key;
  702. input.value = val;
  703. form.appendChild(input);
  704. }
  705. });
  706. form.submit();
  707. form.parentNode.removeChild(form);
  708. }
  709. });
  710. /**
  711. * Copyright Facebook Inc.
  712. *
  713. * Licensed under the Apache License, Version 2.0 (the "License");
  714. * you may not use this file except in compliance with the License.
  715. * You may obtain a copy of the License at
  716. *
  717. * http://www.apache.org/licenses/LICENSE-2.0
  718. *
  719. * Unless required by applicable law or agreed to in writing, software
  720. * distributed under the License is distributed on an "AS IS" BASIS,
  721. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  722. * See the License for the specific language governing permissions and
  723. * limitations under the License.
  724. *
  725. *
  726. *
  727. * @provides fb.flash
  728. * @requires fb.prelude
  729. * fb.qs
  730. * fb.content
  731. */
  732. /**
  733. * Flash Support.
  734. *
  735. * @class FB.Flash
  736. * @static
  737. * @access private
  738. */
  739. FB.provide('Flash', {
  740. //
  741. // DYNAMIC DATA
  742. //
  743. _minVersions: [
  744. [9, 0, 159, 0 ],
  745. [10, 0, 22, 87]
  746. ],
  747. _swfPath: 'swf/XdComm.swf',
  748. /**
  749. * The onReady callbacks.
  750. *
  751. * @access private
  752. * @type Array
  753. */
  754. _callbacks: [],
  755. /**
  756. * Names of embedded swfs. Used for removing on unload.
  757. *
  758. * @access private
  759. * @type Object
  760. */
  761. _names: {},
  762. /**
  763. * Whether or not unload callback has been registered (used in IE9).
  764. *
  765. * @access private
  766. * @type Boolean
  767. */
  768. _unloadRegistered: false,
  769. /**
  770. * Initialize the SWF.
  771. *
  772. * @access private
  773. */
  774. init: function() {
  775. // only initialize once
  776. if (FB.Flash._init) {
  777. return;
  778. }
  779. FB.Flash._init = true;
  780. // the SWF calls this global function to notify that its ready
  781. // FIXME: should allow the SWF to take a flashvar that controls the name
  782. // of this function. we should not have any globals other than FB.
  783. window.FB_OnFlashXdCommReady = function() {
  784. FB.Flash._ready = true;
  785. for (var i=0, l=FB.Flash._callbacks.length; i<l; i++) {
  786. FB.Flash._callbacks[i]();
  787. }
  788. FB.Flash._callbacks = [];
  789. };
  790. FB.Flash.embedSWF('XdComm',
  791. FB.getDomain('cdn_foreign') + FB.Flash._swfPath);
  792. },
  793. /**
  794. * generates the swf <object> tag and drops it in the DOM
  795. *
  796. * @access private
  797. */
  798. embedSWF: function(name, swf, flashvars) {
  799. // create the swf
  800. var
  801. IE = !!document.attachEvent,
  802. html = (
  803. '<object ' +
  804. 'type="application/x-shockwave-flash" ' +
  805. 'id="' + name + '" ' +
  806. (flashvars ? 'flashvars="' + flashvars + '" ' : '') +
  807. (IE ? 'name="' + name + '" ' : '') +
  808. (IE ? '' : 'data="' + swf + '" ') +
  809. (IE
  810. ? 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" '
  811. : ''
  812. ) +
  813. 'allowscriptaccess="always">' +
  814. '<param name="movie" value="' + swf + '"></param>' +
  815. '<param name="allowscriptaccess" value="always"></param>' +
  816. '</object>'
  817. );
  818. FB.Content.appendHidden(html);
  819. if (FB.UA.ie() >= 9) {
  820. if (!FB.Flash._unloadRegistered) {
  821. var unloadcb = function() {
  822. FB.Array.forEach(FB.Flash._names, function(val, key) {
  823. var elem = document.getElementById(key);
  824. if (elem) {
  825. elem.removeNode(true);
  826. }
  827. });
  828. };
  829. window.attachEvent('onunload', unloadcb);
  830. FB.Flash._unloadRegistered = true;
  831. }
  832. FB.Flash._names[name] = true;
  833. }
  834. },
  835. /**
  836. * Check that the minimal version of Flash we need is available.
  837. *
  838. * @access private
  839. * @return {Boolean} true if the minimum version requirements are matched
  840. */
  841. hasMinVersion: function() {
  842. if (typeof FB.Flash._hasMinVersion === 'undefined') {
  843. var
  844. versionString,
  845. i,
  846. l,
  847. version = [];
  848. try {
  849. versionString = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')
  850. .GetVariable('$version');
  851. } catch(x) {
  852. if (navigator.mimeTypes.length > 0) {
  853. var mimeType = 'application/x-shockwave-flash';
  854. if (navigator.mimeTypes[mimeType].enabledPlugin) {
  855. var name = 'Shockwave Flash';
  856. versionString = (navigator.plugins[name + ' 2.0'] ||
  857. navigator.plugins[name])
  858. .description;
  859. }
  860. }
  861. }
  862. // take the string and come up with an array of integers:
  863. // [10, 0, 22]
  864. if (versionString) {
  865. var parts = versionString
  866. .replace(/\D+/g, ',')
  867. .match(/^,?(.+),?$/)[1]
  868. .split(',');
  869. for (i=0, l=parts.length; i<l; i++) {
  870. version.push(parseInt(parts[i], 10));
  871. }
  872. }
  873. // start by assuming we dont have the min version.
  874. FB.Flash._hasMinVersion = false;
  875. // look through all the allowed version definitions.
  876. majorVersion:
  877. for (i=0, l=FB.Flash._minVersions.length; i<l; i++) {
  878. var spec = FB.Flash._minVersions[i];
  879. // we only accept known major versions, and every supported major
  880. // version has at least one entry in _minVersions. only if the major
  881. // version matches, does the rest of the check make sense.
  882. if (spec[0] != version[0]) {
  883. continue;
  884. }
  885. // the rest of the version components must be equal or higher
  886. for (var m=1, n=spec.length, o=version.length; (m<n && m<o); m++) {
  887. if (version[m] < spec[m]) {
  888. // less means this major version is no good
  889. //#JSCOVERAGE_IF 0
  890. FB.Flash._hasMinVersion = false;
  891. continue majorVersion;
  892. //#JSCOVERAGE_ENDIF
  893. } else {
  894. FB.Flash._hasMinVersion = true;
  895. if (version[m] > spec[m]) {
  896. // better than needed
  897. break majorVersion;
  898. }
  899. }
  900. }
  901. }
  902. }
  903. return FB.Flash._hasMinVersion;
  904. },
  905. /**
  906. * Register a function that needs to ensure Flash is ready.
  907. *
  908. * @access private
  909. * @param cb {Function} the function
  910. */
  911. onReady: function(cb) {
  912. FB.Flash.init();
  913. if (FB.Flash._ready) {
  914. // this forces the cb to be asynchronous to ensure no one relies on the
  915. // _potential_ synchronous nature.
  916. window.setTimeout(cb, 0);
  917. } else {
  918. FB.Flash._callbacks.push(cb);
  919. }
  920. }
  921. });
  922. /**
  923. * This is the stock JSON2 implementation from www.json.org.
  924. *
  925. * Modifications include:
  926. * 1/ Removal of jslint settings
  927. *
  928. * @provides fb.thirdparty.json2
  929. */
  930. /*
  931. http://www.JSON.org/json2.js
  932. 2009-09-29
  933. Public Domain.
  934. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
  935. See http://www.JSON.org/js.html
  936. This file creates a global JSON object containing two methods: stringify
  937. and parse.
  938. JSON.stringify(value, replacer, space)
  939. value any JavaScript value, usually an object or array.
  940. replacer an optional parameter that determines how object
  941. values are stringified for objects. It can be a
  942. function or an array of strings.
  943. space an optional parameter that specifies the indentation
  944. of nested structures. If it is omitted, the text will
  945. be packed without extra whitespace. If it is a number,
  946. it will specify the number of spaces to indent at each
  947. level. If it is a string (such as '\t' or '&nbsp;'),
  948. it contains the characters used to indent at each level.
  949. This method produces a JSON text from a JavaScript value.
  950. When an object value is found, if the object contains a toJSON
  951. method, its toJSON method will be called and the result will be
  952. stringified. A toJSON method does not serialize: it returns the
  953. value represented by the name/value pair that should be serialized,
  954. or undefined if nothing should be serialized. The toJSON method
  955. will be passed the key associated with the value, and this will be
  956. bound to the value
  957. For example, this would serialize Dates as ISO strings.
  958. Date.prototype.toJSON = function (key) {
  959. function f(n) {
  960. // Format integers to have at least two digits.
  961. return n < 10 ? '0' + n : n;
  962. }
  963. return this.getUTCFullYear() + '-' +
  964. f(this.getUTCMonth() + 1) + '-' +
  965. f(this.getUTCDate()) + 'T' +
  966. f(this.getUTCHours()) + ':' +
  967. f(this.getUTCMinutes()) + ':' +
  968. f(this.getUTCSeconds()) + 'Z';
  969. };
  970. You can provide an optional replacer method. It will be passed the
  971. key and value of each member, with this bound to the containing
  972. object. The value that is returned from your method will be
  973. serialized. If your method returns undefined, then the member will
  974. be excluded from the serialization.
  975. If the replacer parameter is an array of strings, then it will be
  976. used to select the members to be serialized. It filters the results
  977. such that only members with keys listed in the replacer array are
  978. stringified.
  979. Values that do not have JSON representations, such as undefined or
  980. functions, will not be serialized. Such values in objects will be
  981. dropped; in arrays they will be replaced with null. You can use
  982. a replacer function to replace those with JSON values.
  983. JSON.stringify(undefined) returns undefined.
  984. The optional space parameter produces a stringification of the
  985. value that is filled with line breaks and indentation to make it
  986. easier to read.
  987. If the space parameter is a non-empty string, then that string will
  988. be used for indentation. If the space parameter is a number, then
  989. the indentation will be that many spaces.
  990. Example:
  991. text = JSON.stringify(['e', {pluribus: 'unum'}]);
  992. // text is '["e",{"pluribus":"unum"}]'
  993. text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
  994. // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
  995. text = JSON.stringify([new Date()], function (key, value) {
  996. return this[key] instanceof Date ?
  997. 'Date(' + this[key] + ')' : value;
  998. });
  999. // text is '["Date(---current time---)"]'
  1000. JSON.parse(text, reviver)
  1001. This method parses a JSON text to produce an object or array.
  1002. It can throw a SyntaxError exception.
  1003. The optional reviver parameter is a function that can filter and
  1004. transform the results. It receives each of the keys and values,
  1005. and its return value is used instead of the original value.
  1006. If it returns what it received, then the structure is not modified.
  1007. If it returns undefined then the member is deleted.
  1008. Example:
  1009. // Parse the text. Values that look like ISO date strings will
  1010. // be converted to Date objects.
  1011. myData = JSON.parse(text, function (key, value) {
  1012. var a;
  1013. if (typeof value === 'string') {
  1014. a =
  1015. /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
  1016. if (a) {
  1017. return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
  1018. +a[5], +a[6]));
  1019. }
  1020. }
  1021. return value;
  1022. });
  1023. myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
  1024. var d;
  1025. if (typeof value === 'string' &&
  1026. value.slice(0, 5) === 'Date(' &&
  1027. value.slice(-1) === ')') {
  1028. d = new Date(value.slice(5, -1));
  1029. if (d) {
  1030. return d;
  1031. }
  1032. }
  1033. return value;
  1034. });
  1035. This is a reference implementation. You are free to copy, modify, or
  1036. redistribute.
  1037. This code should be minified before deployment.
  1038. See http://javascript.crockford.com/jsmin.html
  1039. USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
  1040. NOT CONTROL.
  1041. */
  1042. // Create a JSON object only if one does not already exist. We create the
  1043. // methods in a closure to avoid creating global variables.
  1044. if (!this.JSON) {
  1045. this.JSON = {};
  1046. }
  1047. (function () {
  1048. function f(n) {
  1049. // Format integers to have at least two digits.
  1050. return n < 10 ? '0' + n : n;
  1051. }
  1052. if (typeof Date.prototype.toJSON !== 'function') {
  1053. Date.prototype.toJSON = function (key) {
  1054. return isFinite(this.valueOf()) ?
  1055. this.getUTCFullYear() + '-' +
  1056. f(this.getUTCMonth() + 1) + '-' +
  1057. f(this.getUTCDate()) + 'T' +
  1058. f(this.getUTCHours()) + ':' +
  1059. f(this.getUTCMinutes()) + ':' +
  1060. f(this.getUTCSeconds()) + 'Z' : null;
  1061. };
  1062. String.prototype.toJSON =
  1063. Number.prototype.toJSON =
  1064. Boolean.prototype.toJSON = function (key) {
  1065. return this.valueOf();
  1066. };
  1067. }
  1068. var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  1069. escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  1070. gap,
  1071. indent,
  1072. meta = { // table of character substitutions
  1073. '\b': '\\b',
  1074. '\t': '\\t',
  1075. '\n': '\\n',
  1076. '\f': '\\f',
  1077. '\r': '\\r',
  1078. '"' : '\\"',
  1079. '\\': '\\\\'
  1080. },
  1081. rep;
  1082. function quote(string) {
  1083. // If the string contains no control characters, no quote characters, and no
  1084. // backslash characters, then we can safely slap some quotes around it.
  1085. // Otherwise we must also replace the offending characters with safe escape
  1086. // sequences.
  1087. escapable.lastIndex = 0;
  1088. return escapable.test(string) ?
  1089. '"' + string.replace(escapable, function (a) {
  1090. var c = meta[a];
  1091. return typeof c === 'string' ? c :
  1092. '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  1093. }) + '"' :
  1094. '"' + string + '"';
  1095. }
  1096. function str(key, holder) {
  1097. // Produce a string from holder[key].
  1098. var i, // The loop counter.
  1099. k, // The member key.
  1100. v, // The member value.
  1101. length,
  1102. mind = gap,
  1103. partial,
  1104. value = holder[key];
  1105. // If the value has a toJSON method, call it to obtain a replacement value.
  1106. if (value && typeof value === 'object' &&
  1107. typeof value.toJSON === 'function') {
  1108. value = value.toJSON(key);
  1109. }
  1110. // If we were called with a replacer function, then call the replacer to
  1111. // obtain a replacement value.
  1112. if (typeof rep === 'function') {
  1113. value = rep.call(holder, key, value);
  1114. }
  1115. // What happens next depends on the value's type.
  1116. switch (typeof value) {
  1117. case 'string':
  1118. return quote(value);
  1119. case 'number':
  1120. // JSON numbers must be finite. Encode non-finite numbers as null.
  1121. return isFinite(value) ? String(value) : 'null';
  1122. case 'boolean':
  1123. case 'null':
  1124. // If the value is a boolean or null, convert it to a string. Note:
  1125. // typeof null does not produce 'null'. The case is included here in
  1126. // the remote chance that this gets fixed someday.
  1127. return String(value);
  1128. // If the type is 'object', we might be dealing with an object or an array or
  1129. // null.
  1130. case 'object':
  1131. // Due to a specification blunder in ECMAScript, typeof null is 'object',
  1132. // so watch out for that case.
  1133. if (!value) {
  1134. return 'null';
  1135. }
  1136. // Make an array to hold the partial results of stringifying this object value.
  1137. gap += indent;
  1138. partial = [];
  1139. // Is the value an array?
  1140. if (Object.prototype.toString.apply(value) === '[object Array]') {
  1141. // The value is an array. Stringify every element. Use null as a placeholder
  1142. // for non-JSON values.
  1143. length = value.length;
  1144. for (i = 0; i < length; i += 1) {
  1145. partial[i] = str(i, value) || 'null';
  1146. }
  1147. // Join all of the elements together, separated with commas, and wrap them in
  1148. // brackets.
  1149. v = partial.length === 0 ? '[]' :
  1150. gap ? '[\n' + gap +
  1151. partial.join(',\n' + gap) + '\n' +
  1152. mind + ']' :
  1153. '[' + partial.join(',') + ']';
  1154. gap = mind;
  1155. return v;
  1156. }
  1157. // If the replacer is an array, use it to select the members to be stringified.
  1158. if (rep && typeof rep === 'object') {
  1159. length = rep.length;
  1160. for (i = 0; i < length; i += 1) {
  1161. k = rep[i];
  1162. if (typeof k === 'string') {
  1163. v = str(k, value);
  1164. if (v) {
  1165. partial.push(quote(k) + (gap ? ': ' : ':') + v);
  1166. }
  1167. }
  1168. }
  1169. } else {
  1170. // Otherwise, iterate through all of the keys in the object.
  1171. for (k in value) {
  1172. if (Object.hasOwnProperty.call(value, k)) {
  1173. v = str(k, value);
  1174. if (v) {
  1175. partial.push(quote(k) + (gap ? ': ' : ':') + v);
  1176. }
  1177. }
  1178. }
  1179. }
  1180. // Join all of the member texts together, separated with commas,
  1181. // and wrap them in braces.
  1182. v = partial.length === 0 ? '{}' :
  1183. gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
  1184. mind + '}' : '{' + partial.join(',') + '}';
  1185. gap = mind;
  1186. return v;
  1187. }
  1188. }
  1189. // If the JSON object does not yet have a stringify method, give it one.
  1190. if (typeof JSON.stringify !== 'function') {
  1191. JSON.stringify = function (value, replacer, space) {
  1192. // The stringify method takes a value and an optional replacer, and an optional
  1193. // space parameter, and returns a JSON text. The replacer can be a function
  1194. // that can replace values, or an array of strings that will select the keys.
  1195. // A default replacer method can be provided. Use of the space parameter can
  1196. // produce text that is more easily readable.
  1197. var i;
  1198. gap = '';
  1199. indent = '';
  1200. // If the space parameter is a number, make an indent string containing that
  1201. // many spaces.
  1202. if (typeof space === 'number') {
  1203. for (i = 0; i < space; i += 1) {
  1204. indent += ' ';
  1205. }
  1206. // If the space parameter is a string, it will be used as the indent string.
  1207. } else if (typeof space === 'string') {
  1208. indent = space;
  1209. }
  1210. // If there is a replacer, it must be a function or an array.
  1211. // Otherwise, throw an error.
  1212. rep = replacer;
  1213. if (replacer && typeof replacer !== 'function' &&
  1214. (typeof replacer !== 'object' ||
  1215. typeof replacer.length !== 'number')) {
  1216. throw new Error('JSON.stringify');
  1217. }
  1218. // Make a fake root object containing our value under the key of ''.
  1219. // Return the result of stringifying the value.
  1220. return str('', {'': value});
  1221. };
  1222. }
  1223. // If the JSON object does not yet have a parse method, give it one.
  1224. if (typeof JSON.parse !== 'function') {
  1225. JSON.parse = function (text, reviver) {
  1226. // The parse method takes a text and an optional reviver function, and returns
  1227. // a JavaScript value if the text is a valid JSON text.
  1228. var j;
  1229. function walk(holder, key) {
  1230. // The walk method is used to recursively walk the resulting structure so
  1231. // that modifications can be made.
  1232. var k, v, value = holder[key];
  1233. if (value && typeof value === 'object') {
  1234. for (k in value) {
  1235. if (Object.hasOwnProperty.call(value, k)) {
  1236. v = walk(value, k);
  1237. if (v !== undefined) {
  1238. value[k] = v;
  1239. } else {
  1240. delete value[k];
  1241. }
  1242. }
  1243. }
  1244. }
  1245. return reviver.call(holder, key, value);
  1246. }
  1247. // Parsing happens in four stages. In the first stage, we replace certain
  1248. // Unicode characters with escape sequences. JavaScript handles many characters
  1249. // incorrectly, either silently deleting them, or treating them as line endings.
  1250. cx.lastIndex = 0;
  1251. if (cx.test(text)) {
  1252. text = text.replace(cx, function (a) {
  1253. return '\\u' +
  1254. ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  1255. });
  1256. }
  1257. // In the second stage, we run the text against regular expressions that look
  1258. // for non-JSON patterns. We are especially concerned with '()' and 'new'
  1259. // because they can cause invocation, and '=' because it can cause mutation.
  1260. // But just to be safe, we want to reject all unexpected forms.
  1261. // We split the second stage into 4 regexp operations in order to work around
  1262. // crippling inefficiencies in IE's and Safari's regexp engines. First we
  1263. // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
  1264. // replace all simple value tokens with ']' characters. Third, we delete all
  1265. // open brackets that follow a colon or comma or that begin the text. Finally,
  1266. // we look to see that the remaining characters are only whitespace or ']' or
  1267. // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
  1268. if (/^[\],:{}\s]*$/.
  1269. test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
  1270. replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
  1271. replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
  1272. // In the third stage we use the eval function to compile the text into a
  1273. // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
  1274. // in JavaScript: it can begin a block or an object literal. We wrap the text
  1275. // in parens to eliminate the ambiguity.
  1276. j = eval('(' + text + ')');
  1277. // In the optional fourth stage, we recursively walk the new structure, passing
  1278. // each name/value pair to a reviver function for possible transformation.
  1279. return typeof reviver === 'function' ?
  1280. walk({'': j}, '') : j;
  1281. }
  1282. // If the text is not JSON parseable, then a SyntaxError is thrown.
  1283. throw new SyntaxError('JSON.parse');
  1284. };
  1285. }
  1286. }());
  1287. /**
  1288. * Copyright Facebook Inc.
  1289. *
  1290. * Licensed under the Apache License, Version 2.0 (the "License");
  1291. * you may not use this file except in compliance with the License.
  1292. * You may obtain a copy of the License at
  1293. *
  1294. * http://www.apache.org/licenses/LICENSE-2.0
  1295. *
  1296. * Unless required by applicable law or agreed to in writing, software
  1297. * distributed under the License is distributed on an "AS IS" BASIS,
  1298. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1299. * See the License for the specific language governing permissions and
  1300. * limitations under the License.
  1301. *
  1302. * @provides fb.json
  1303. * @requires fb.prelude
  1304. * fb.thirdparty.json2
  1305. */
  1306. /**
  1307. * Simple wrapper around standard JSON to handle third-party library quirks.
  1308. *
  1309. * @class FB.JSON
  1310. * @static
  1311. * @access private
  1312. */
  1313. FB.provide('JSON', {
  1314. /**
  1315. * Stringify an object.
  1316. *
  1317. * @param obj {Object} the input object
  1318. * @return {String} the JSON string
  1319. */
  1320. stringify: function(obj) {
  1321. // PrototypeJS is incompatible with native JSON or JSON2 (which is what
  1322. // native JSON is based on)
  1323. if (window.Prototype && Object.toJSON) {
  1324. return Object.toJSON(obj);
  1325. } else {
  1326. return JSON.stringify(obj);
  1327. }
  1328. },
  1329. /**
  1330. * Parse a JSON string.
  1331. *
  1332. * @param str {String} the JSON string
  1333. * @param {Object} the parsed object
  1334. */
  1335. parse: function(str) {
  1336. return JSON.parse(str);
  1337. },
  1338. /**
  1339. * Flatten an object to "stringified" values only. This is useful as a
  1340. * pre-processing query strings where the server expects query parameter
  1341. * values to be JSON encoded.
  1342. *
  1343. * @param obj {Object} the input object
  1344. * @return {Object} object with only string values
  1345. */
  1346. flatten: function(obj) {
  1347. var flat = {};
  1348. for (var key in obj) {
  1349. if (obj.hasOwnProperty(key)) {
  1350. var value = obj[key];
  1351. if (null === value || undefined === value) {
  1352. continue;
  1353. } else if (typeof value == 'string') {
  1354. flat[key] = value;
  1355. } else {
  1356. flat[key] = FB.JSON.stringify(value);
  1357. }
  1358. }
  1359. }
  1360. return flat;
  1361. }
  1362. });
  1363. /**
  1364. * Copyright Facebook Inc.
  1365. *
  1366. * Licensed under the Apache License, Version 2.0 (the "License");
  1367. * you may not use this file except in compliance with the License.
  1368. * You may obtain a copy of the License at
  1369. *
  1370. * http://www.apache.org/licenses/LICENSE-2.0
  1371. *
  1372. * Unless required by applicable law or agreed to in writing, software
  1373. * distributed under the License is distributed on an "AS IS" BASIS,
  1374. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1375. * See the License for the specific language governing permissions and
  1376. * limitations under the License.
  1377. *
  1378. *
  1379. *
  1380. * Contains the public method ``FB.api`` and the internal implementation
  1381. * ``FB.ApiServer``.
  1382. *
  1383. * @provides fb.api
  1384. * @requires fb.prelude
  1385. * fb.qs
  1386. * fb.flash
  1387. * fb.json
  1388. */
  1389. /**
  1390. * API calls.
  1391. *
  1392. * @class FB
  1393. * @static
  1394. * @access private
  1395. */
  1396. FB.provide('', {
  1397. /**
  1398. * Make a API call to the [Graph API](/docs/api).
  1399. *
  1400. * Server-side calls are available via the JavaScript SDK that allow you to
  1401. * build rich applications that can make API calls against the Facebook
  1402. * servers directly from the user's browser. This can improve performance in
  1403. * many scenarios, as compared to making all calls from your server. It can
  1404. * also help reduce, or eliminate the need to proxy the requests thru your
  1405. * own servers, freeing them to do other things.
  1406. *
  1407. * The range of APIs available covers virtually all facets of Facebook.
  1408. * Public data such as [names][names] and [profile pictures][profilepic] are
  1409. * available if you know the id of the user or object. Various parts of the
  1410. * API are available depending on the [connect status and the
  1411. * permissions](FB.login) the user has granted your application.
  1412. *
  1413. * Except the path, all arguments to this function are optional.
  1414. *
  1415. * Get the **f8 Page Object**:
  1416. *
  1417. * FB.api('/f8', function(response) {
  1418. * alert(response.company_overview);
  1419. * });
  1420. *
  1421. * If you have an [authenticated user](FB.login), get their **User Object**:
  1422. *
  1423. * FB.api('/me', function(response) {
  1424. * alert(response.name);
  1425. * });
  1426. *
  1427. * Get the 3 most recent **Post Objects** *Connected* to (in other words,
  1428. * authored by) the *f8 Page Object*:
  1429. *
  1430. * FB.api('/f8/posts', { limit: 3 }, function(response) {
  1431. * for (var i=0, l=response.length; i<l; i++) {
  1432. * var post = response[i];
  1433. * if (post.message) {
  1434. * alert('Message: ' + post.message);
  1435. * } else if (post.attachment && post.attachment.name) {
  1436. * alert('Attachment: ' + post.attachment.name);
  1437. * }
  1438. * }
  1439. * });
  1440. *
  1441. * If you have an [authenticated user](FB.login) with the
  1442. * [publish_stream](/docs/authentication/permissions) permission, and want
  1443. * to publish a new story to their feed:
  1444. *
  1445. * var body = 'Reading Connect JS documentation';
  1446. * FB.api('/me/feed', 'post', { body: body }, function(response) {
  1447. * if (!response || response.error) {
  1448. * alert('Error occurred');
  1449. * } else {
  1450. * alert('Post ID: ' + response);
  1451. * }
  1452. * });
  1453. *
  1454. * Or if you want a delete a previously published post:
  1455. *
  1456. * var postId = '1234567890';
  1457. * FB.api(postId, 'delete', function(response) {
  1458. * if (!response || response.error) {
  1459. * alert('Error occurred');
  1460. * } else {
  1461. * alert('Post was deleted');
  1462. * }
  1463. * });
  1464. *
  1465. *
  1466. * ### Old REST API calls
  1467. *
  1468. * This method can also be used to invoke calls to the
  1469. * [Old REST API](../rest/). The function signature for invoking REST API
  1470. * calls is:
  1471. *
  1472. * FB.api(params, callback)
  1473. *
  1474. * For example, to invoke [links.getStats](../rest/links.getStats):
  1475. *
  1476. * FB.api(
  1477. * {
  1478. * method: 'links.getStats',
  1479. * urls: 'facebook.com,developers.facebook.com'
  1480. * },
  1481. * function(response) {
  1482. * alert(
  1483. * 'Total: ' + (response[0].total_count + response[1].total_count));
  1484. * }
  1485. * );
  1486. *
  1487. * [names]: https://graph.facebook.com/naitik
  1488. * [profilepic]: https://graph.facebook.com/naitik/picture
  1489. *
  1490. * @access public
  1491. * @param path {String} the url path
  1492. * @param method {String} the http method (default `"GET"`)
  1493. * @param params {Object} the parameters for the query
  1494. * @param cb {Function} the callback function to handle the response
  1495. */
  1496. api: function() {
  1497. if (typeof arguments[0] === 'string') {
  1498. FB.ApiServer.graph.apply(FB.ApiServer, arguments);
  1499. } else {
  1500. FB.ApiServer.rest.apply(FB.ApiServer, arguments);
  1501. }
  1502. }
  1503. });
  1504. /**
  1505. * API call implementations.
  1506. *
  1507. * @class FB.ApiServer
  1508. * @access private
  1509. */
  1510. FB.provide('ApiServer', {
  1511. METHODS: ['get', 'post', 'delete', 'put'],
  1512. _callbacks: {},
  1513. _readOnlyCalls: {
  1514. fql_query: true,
  1515. fql_multiquery: true,
  1516. friends_get: true,
  1517. notifications_get: true,
  1518. stream_get: true,
  1519. users_getinfo: true
  1520. },
  1521. /**
  1522. * Make a API call to Graph server. This is the **real** RESTful API.
  1523. *
  1524. * Except the path, all arguments to this function are optional. So any of
  1525. * these are valid:
  1526. *
  1527. * FB.api('/me') // throw away the response
  1528. * FB.api('/me', function(r) { console.log(r) })
  1529. * FB.api('/me', { fields: 'email' }); // throw away response
  1530. * FB.api('/me', { fields: 'email' }, function(r) { console.log(r) });
  1531. * FB.api('/12345678', 'delete', function(r) { console.log(r) });
  1532. * FB.api(
  1533. * '/me/feed',
  1534. * 'post',
  1535. * { body: 'hi there' },
  1536. * function(r) { console.log(r) }
  1537. * );
  1538. *
  1539. * @access private
  1540. * @param path {String} the url path
  1541. * @param method {String} the http method
  1542. * @param params {Object} the parameters for the query
  1543. * @param cb {Function} the callback function to handle the response
  1544. */
  1545. graph: function() {
  1546. var
  1547. args = Array.prototype.slice.call(arguments),
  1548. atoms = args.shift().match(/\/?([^?]*)\??([^#]*)/),
  1549. path = atoms[1],
  1550. next = args.shift(),
  1551. method,
  1552. params,
  1553. cb;
  1554. while (next) {
  1555. var type = typeof next;
  1556. if (type === 'string' && !method) {
  1557. method = next.toLowerCase();
  1558. } else if (type === 'function' && !cb) {
  1559. cb = next;
  1560. } else if (type === 'object' && !params) {
  1561. params = next;
  1562. } else {
  1563. FB.log('Invalid argument passed to FB.api(): ' + next);
  1564. return;
  1565. }
  1566. next = args.shift();
  1567. }
  1568. method = method || 'get';
  1569. params = FB.copy(params || {}, FB.QS.decode(atoms[2]));
  1570. if (FB.Array.indexOf(FB.ApiServer.METHODS, method) < 0) {
  1571. FB.log('Invalid method passed to FB.api(): ' + method);
  1572. return;
  1573. }
  1574. FB.ApiServer.oauthRequest('graph', path, method, params, cb);
  1575. },
  1576. /**
  1577. * Old school restserver.php calls.
  1578. *
  1579. * @access private
  1580. * @param params {Object} The required arguments vary based on the method
  1581. * being used, but specifying the method itself is mandatory:
  1582. *
  1583. * Property | Type | Description | Argument
  1584. * -------- | ------- | -------------------------------- | ------------
  1585. * method | String | The API method to invoke. | **Required**
  1586. * @param cb {Function} The callback function to handle the response.
  1587. */
  1588. rest: function(params, cb) {
  1589. var method = params.method.toLowerCase().replace('.', '_');
  1590. // this is an optional dependency on FB.Auth
  1591. // Auth.revokeAuthorization affects the session
  1592. if (FB.Auth && method === 'auth_revokeauthorization') {
  1593. var old_cb = cb;
  1594. cb = function(response) {
  1595. if (response === true) {
  1596. FB.Auth.setAuthResponse(null, 'not_authorized');
  1597. }
  1598. old_cb && old_cb(response);
  1599. };
  1600. }
  1601. params.format = 'json-strings';
  1602. params.api_key = FB._apiKey;
  1603. var domain = FB.ApiServer._readOnlyCalls[method] ? 'api_read' : 'api';
  1604. FB.ApiServer.oauthRequest(domain, 'restserver.php', 'get', params, cb);
  1605. },
  1606. /**
  1607. * Add the oauth parameter, and fire off a request.
  1608. *
  1609. * @access private
  1610. * @param domain {String} the domain key, one of 'api', 'api_read',
  1611. * or 'graph'
  1612. * @param path {String} the request path
  1613. * @param method {String} the http method
  1614. * @param params {Object} the parameters for the query
  1615. * @param cb {Function} the callback function to handle the response
  1616. */
  1617. oauthRequest: function(domain, path, method, params, cb) {
  1618. if (!params.access_token && FB.getAccessToken()) {
  1619. params.access_token = FB.getAccessToken();
  1620. }
  1621. params.sdk = 'joey';
  1622. params.pretty = 0; // browser's default to pretty=1, explicitly setting to
  1623. // 0 will save a few bytes
  1624. // wrap the callback to force fetch login status if we had a bad access
  1625. // token when we made the api call and it hadn't changed between the
  1626. // call firing and the response coming in.
  1627. var oldCb = cb;
  1628. cb = function(response) {
  1629. if (FB.Auth && response && FB.getAccessToken() == params.access_token &&
  1630. (response.error_code === '190' ||
  1631. (response.error &&
  1632. (response.error === 'invalid_token' ||
  1633. response.error.type === 'OAuthException')))) {
  1634. FB.getLoginStatus(null, true);
  1635. }
  1636. oldCb && oldCb(response);
  1637. };
  1638. try {
  1639. FB.ApiServer.jsonp(domain, path, method, FB.JSON.flatten(params), cb);
  1640. } catch (e1_ignore) {
  1641. try {
  1642. if (!FB.initSitevars.corsKillSwitch &&
  1643. FB.ApiServer.corsPost(
  1644. domain, path, method, FB.JSON.flatten(params), cb)) {
  1645. return;
  1646. }
  1647. } catch (e2_ignore) {
  1648. // do nothing... fall back to flash.
  1649. }
  1650. if (FB.Flash.hasMinVersion()) {
  1651. FB.ApiServer.flash(domain, path, method, FB.JSON.flatten(params), cb);
  1652. } else {
  1653. throw new Error('Your browser does not support long connect ' +
  1654. 'requests. You can fix this problem by upgrading your browser ' +
  1655. 'or installing the latest version of Flash');
  1656. }
  1657. }
  1658. },
  1659. corsPost: function(domain, path, method, params, cb) {
  1660. var url = FB.getDomain(domain) + path;
  1661. if (domain == 'graph') {
  1662. params.method = method;
  1663. }
  1664. var encoded_params = FB.QS.encode(params);
  1665. var content_type = 'application/x-www-form-urlencoded';
  1666. var request = FB.ApiServer._createCORSRequest('POST', url, content_type);
  1667. if (request) {
  1668. request.onload = function() {
  1669. cb && cb(FB.JSON.parse(request.responseText));
  1670. };
  1671. request.send(encoded_params);
  1672. return true;
  1673. } else {
  1674. return false;
  1675. }
  1676. },
  1677. _createCORSRequest: function(method, url, content_type) {
  1678. if (!window.XMLHttpRequest) {
  1679. return null;
  1680. }
  1681. var xhr = new XMLHttpRequest();
  1682. if ("withCredentials" in xhr) {
  1683. xhr.open(method, url, true);
  1684. xhr.setRequestHeader('Content-type', content_type);
  1685. } else if (window.XDomainRequest) {
  1686. xhr = new XDomainRequest();
  1687. xhr.open(method, url);
  1688. } else {
  1689. xhr = null;
  1690. }
  1691. return xhr;
  1692. },
  1693. /**
  1694. * Basic JSONP Support.
  1695. *
  1696. * @access private
  1697. * @param domain {String} the domain key, one of 'api', 'api_read',
  1698. * or 'graph'
  1699. * @param path {String} the request path
  1700. * @param method {String} the http method
  1701. * @param params {Object} the parameters for the query
  1702. * @param cb {Function} the callback function to handle the response
  1703. */
  1704. jsonp: function(domain, path, method, params, cb) {
  1705. var
  1706. g = FB.guid(),
  1707. script = document.createElement('script');
  1708. // jsonp needs method overrides as the request itself is always a GET
  1709. if (domain === 'graph' && method !== 'get') {
  1710. params.method = method;
  1711. }
  1712. params.callback = 'FB.ApiServer._callbacks.' + g;
  1713. var url = (
  1714. FB.getDomain(domain) + path +
  1715. (path.indexOf('?') > -1 ? '&' : '?') +
  1716. FB.QS.encode(params)
  1717. );
  1718. if (url.length > 2000) {
  1719. throw new Error('JSONP only support a maximum of 2000 bytes of input.');
  1720. }
  1721. // this is the JSONP callback invoked by the response
  1722. FB.ApiServer._callbacks[g] = function(response) {
  1723. cb && cb(response);
  1724. delete FB.ApiServer._callbacks[g];
  1725. script.parentNode.removeChild(script);
  1726. };
  1727. script.src = url;
  1728. document.getElementsByTagName('head')[0].appendChild(script);
  1729. },
  1730. /**
  1731. * Flash based HTTP Client.
  1732. *
  1733. * @access private
  1734. * @param domain {String} the domain key, one of 'api' or 'graph'
  1735. * @param path {String} the request path
  1736. * @param method {String} the http method
  1737. * @param params {Object} the parameters for the query
  1738. * @param cb {Function} the callback function to handle the response
  1739. */
  1740. flash: function(domain, path, method, params, cb) {
  1741. if (!window.FB_OnXdHttpResult) {
  1742. // the SWF calls this global function when a HTTP response is available
  1743. // FIXME: remove global
  1744. window.FB_OnXdHttpResult = function(reqId, data) {
  1745. FB.ApiServer._callbacks[reqId](decodeURIComponent(data));
  1746. };
  1747. }
  1748. FB.Flash.onReady(function() {
  1749. if (domain === 'graph') {
  1750. params.suppress_http_code = 1;
  1751. }
  1752. var
  1753. url = FB.getDomain(domain) + path,
  1754. body = FB.QS.encode(params);
  1755. if (method === 'get') {
  1756. // convert GET to POST if needed based on URL length
  1757. if (url.length + body.length > 2000) {
  1758. if (domain === 'graph') {
  1759. params.method = 'get';
  1760. }
  1761. method = 'post';
  1762. body = FB.QS.encode(params);
  1763. } else {
  1764. url += (url.indexOf('?') > -1 ? '&' : '?') + body;
  1765. body = '';
  1766. }
  1767. } else if (method !== 'post') {
  1768. // we use method override and do a POST for PUT/DELETE as flash has
  1769. // trouble otherwise
  1770. if (domain === 'graph') {
  1771. params.method = method;
  1772. }
  1773. method = 'post';
  1774. body = FB.QS.encode(params);
  1775. }
  1776. // fire the request
  1777. var reqId = document.XdComm.sendXdHttpRequest(
  1778. method.toUpperCase(), url, body, null);
  1779. // callback
  1780. FB.ApiServer._callbacks[reqId] = function(response) {
  1781. cb && cb(FB.JSON.parse(response));
  1782. delete FB.ApiServer._callbacks[reqId];
  1783. };
  1784. });
  1785. }
  1786. });
  1787. /**
  1788. * Copyright Facebook Inc.
  1789. *
  1790. * Licensed under the Apache License, Version 2.0 (the "License");
  1791. * you may not use this file except in compliance with the License.
  1792. * You may obtain a copy of the License at
  1793. *
  1794. * http://www.apache.org/licenses/LICENSE-2.0
  1795. *
  1796. * Unless required by applicable law or agreed to in writing, software
  1797. * distributed under the License is distributed on an "AS IS" BASIS,
  1798. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1799. * See the License for the specific language governing permissions and
  1800. * limitations under the License.
  1801. *
  1802. * @provides fb.event
  1803. * @requires fb.prelude fb.array
  1804. */
  1805. // NOTE: We tag this as FB.Event even though it is actually FB.EventProvider to
  1806. // work around limitations in the documentation system.
  1807. /**
  1808. * Event handling mechanism for globally named events.
  1809. *
  1810. * @static
  1811. * @class FB.Event
  1812. */
  1813. FB.provide('EventProvider', {
  1814. /**
  1815. * Returns the internal subscriber array that can be directly manipulated by
  1816. * adding/removing things.
  1817. *
  1818. * @access private
  1819. * @return {Object}
  1820. */
  1821. subscribers: function() {
  1822. // this odd looking logic is to allow instances to lazily have a map of
  1823. // their events. if subscribers were an object literal itself, we would
  1824. // have issues with instances sharing the subscribers when its being used
  1825. // in a mixin style.
  1826. if (!this._subscribersMap) {
  1827. this._subscribersMap = {};
  1828. }
  1829. return this._subscribersMap;
  1830. },
  1831. /**
  1832. * Subscribe to a given event name, invoking your callback function whenever
  1833. * the event is fired.
  1834. *
  1835. * For example, suppose you want to get notified whenever the authResponse
  1836. * changes:
  1837. *
  1838. * FB.Event.subscribe('auth.authResponse', function(response) {
  1839. * // do something with response.access_token
  1840. * });
  1841. *
  1842. * Global Events:
  1843. *
  1844. * - auth.login -- fired when the user logs in
  1845. * - auth.logout -- fired when the user logs out
  1846. * - auth.prompt -- fired when the user is prompted to log-in/opt-in
  1847. * - auth.authResponseChange -- fired when the authResponse changes
  1848. * - auth.accessTokenChange -- fired when the access token changes.
  1849. * - auth.statusChange -- fired when the status changes
  1850. * - xfbml.parse -- firest when a call to FB.XFBML.parse()
  1851. * has processed all XFBML tags in the
  1852. * element.process() sense
  1853. * - xfbml.render -- fired when a call to FB.XFBML.parse() completes
  1854. * - edge.create -- fired when the user likes something (fb:like)
  1855. * - comments.add -- fired when the user adds a comment (fb:comments)
  1856. * - question.firstVote -- fired when user initially votes on a poll
  1857. * (fb:question)
  1858. * - question.vote -- fired when user votes again on a poll (fb:question)
  1859. * - fb.log -- fired on log message
  1860. * - canvas.pageInfoChange -- fired when the page is resized or scrolled
  1861. *
  1862. * @access public
  1863. * @param name {String} Name of the event.
  1864. * @param cb {Function} The handler function.
  1865. */
  1866. subscribe: function(name, cb) {
  1867. var subs = this.subscribers();
  1868. if (!subs[name]) {
  1869. subs[name] = [cb];
  1870. } else {
  1871. subs[name].push(cb);
  1872. }
  1873. },
  1874. /**
  1875. * Removes subscribers, inverse of [FB.Event.subscribe](FB.Event.subscribe).
  1876. *
  1877. * Removing a subscriber is basically the same as adding one. You need to
  1878. * pass the same event name and function to unsubscribe that you passed into
  1879. * subscribe. If we use a similar example to
  1880. * [FB.Event.subscribe](FB.event.subscribe), we get:
  1881. *
  1882. * var onAuthResponseChange = function(response) {
  1883. * // do something with response.access_token
  1884. * };
  1885. * FB.Event.subscribe('auth.authResponseChange', onAuthResponseChange);
  1886. *
  1887. * // sometime later in your code you dont want to get notified anymore
  1888. * FB.Event.unsubscribe('auth.authResponseChange', onAuthResponseChange);
  1889. *
  1890. * @access public
  1891. * @param name {String} Name of the event.
  1892. * @param cb {Function} The handler function.
  1893. */
  1894. unsubscribe: function(name, cb) {
  1895. var subs = this.subscribers()[name];
  1896. FB.Array.forEach(subs, function(value, key) {
  1897. if (value == cb) {
  1898. subs[key] = null;
  1899. }
  1900. });
  1901. },
  1902. /**
  1903. * Repeatedly listen for an event over time. The callback is invoked
  1904. * immediately when monitor is called, and then every time the event
  1905. * fires. The subscription is canceled when the callback returns true.
  1906. *
  1907. * @access private
  1908. * @param {string} name Name of event.
  1909. * @param {function} callback A callback function. Any additional arguments
  1910. * to monitor() will be passed on to the callback. When the callback returns
  1911. * true, the monitoring will cease.
  1912. */
  1913. monitor: function(name, callback) {
  1914. if (!callback()) {
  1915. var
  1916. ctx = this,
  1917. fn = function() {
  1918. if (callback.apply(callback, arguments)) {
  1919. ctx.unsubscribe(name, fn);
  1920. }
  1921. };
  1922. this.subscribe(name, fn);
  1923. }
  1924. },
  1925. /**
  1926. * Removes all subscribers for named event.
  1927. *
  1928. * You need to pass the same event name that was passed to FB.Event.subscribe.
  1929. * This is useful if the event is no longer worth listening to and you
  1930. * believe that multiple subscribers have been set up.
  1931. *
  1932. * @access private
  1933. * @param name {String} name of the event
  1934. */
  1935. clear: function(name) {
  1936. delete this.subscribers()[name];
  1937. },
  1938. /**
  1939. * Fires a named event. The first argument is the name, the rest of the
  1940. * arguments are passed to the subscribers.
  1941. *
  1942. * @access private
  1943. * @param name {String} the event name
  1944. */
  1945. fire: function() {
  1946. var
  1947. args = Array.prototype.slice.call(arguments),
  1948. name = args.shift();
  1949. FB.Array.forEach(this.subscribers()[name], function(sub) {
  1950. // this is because we sometimes null out unsubscribed rather than jiggle
  1951. // the array
  1952. if (sub) {
  1953. sub.apply(this, args);
  1954. }
  1955. });
  1956. },
  1957. //////////////////////////////////////////////////////////////////////////////
  1958. // DOM Events
  1959. //////////////////////////////////////////////////////////////////////////////
  1960. /**
  1961. * Listen to `event` with the `func` event handler.
  1962. */
  1963. listen: function(element, event, func) {
  1964. if (element.addEventListener) {
  1965. element.addEventListener(event, func, false);
  1966. } else if (element.attachEvent) {
  1967. element.attachEvent('on' + event, func);
  1968. }
  1969. },
  1970. /**
  1971. * Do not listen to `event` with the `func` event handler.
  1972. */
  1973. unlisten: function(element, event, func) {
  1974. if (element.removeEventListener) {
  1975. element.removeEventListener(event, func, false);
  1976. } else if (element.detachEvent) {
  1977. element.detachEvent('on' + event, func);
  1978. }
  1979. }
  1980. });
  1981. /**
  1982. * Event handling mechanism for globally named events.
  1983. *
  1984. * @class FB.Event
  1985. * @extends FB.EventProvider
  1986. */
  1987. FB.provide('Event', FB.EventProvider);
  1988. /**
  1989. * Copyright Facebook Inc.
  1990. *
  1991. * Licensed under the Apache License, Version 2.0 (the "License");
  1992. * you may not use this file except in compliance with the License.
  1993. * You may obtain a copy of the License at
  1994. *
  1995. * http://www.apache.org/licenses/LICENSE-2.0
  1996. *
  1997. * Unless required by applicable law or agreed to in writing, software
  1998. * distributed under the License is distributed on an "AS IS" BASIS,
  1999. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2000. * See the License for the specific language governing permissions and
  2001. * limitations under the License.
  2002. *
  2003. *
  2004. *
  2005. * @provides fb.xd
  2006. * @requires fb.prelude
  2007. * fb.qs
  2008. * fb.flash
  2009. */
  2010. /**
  2011. * The cross domain communication layer.
  2012. *
  2013. * @class FB.XD
  2014. * @static
  2015. * @access private
  2016. */
  2017. FB.provide('XD', {
  2018. _origin : null,
  2019. _transport : null,
  2020. _callbacks : {},
  2021. _forever : {},
  2022. _xdProxyUrl : 'connect/xd_proxy.php',
  2023. // For certain versions of IE, we delay the choice of transport and
  2024. // origin until we're in the handler
  2025. _openerTransport : null,
  2026. _openerOrigin : null,
  2027. _nonOpenerOrigin : null,
  2028. /**
  2029. * Initialize the XD layer. Native postMessage or Flash is required.
  2030. *
  2031. * @param channelUrl {String} optional channel URL
  2032. * @access private
  2033. */
  2034. init: function(channelUrl) {
  2035. // only do init once, if this is set, we're already done
  2036. if (FB.XD._origin) {
  2037. return;
  2038. }
  2039. //#JSCOVERAGE_IF
  2040. // The origin here is used for postMessage security. It needs to be based
  2041. // on the URL of the current window. It is required and validated by
  2042. // Facebook as part of the xd_proxy.php.
  2043. var postMessageOrigin = (window.location.protocol + '//' +
  2044. window.location.host + '/' + FB.guid());
  2045. if (window.addEventListener && !window.attachEvent && window.postMessage) {
  2046. FB.XD._origin = postMessageOrigin;
  2047. FB.XD.PostMessage.init();
  2048. FB.XD._transport = 'postmessage';
  2049. } else if (!channelUrl && FB.Flash.hasMinVersion()) {
  2050. if (document.getElementById('fb-root')) {
  2051. var domain = document.domain;
  2052. // If we're loading from facebook.com, it's safe to take the entire
  2053. // location
  2054. if (domain == 'facebook.com') {
  2055. domain = window.location.host;
  2056. }
  2057. // The origin here is used for Flash XD security. It needs to
  2058. // be based on document.domain rather than the URL of the
  2059. // current window. It is required and validated by Facebook as
  2060. // part of the xd_proxy.php.
  2061. FB.XD._origin = (window.location.protocol + '//' + domain +
  2062. '/' + FB.guid());
  2063. FB.XD.Flash.init();
  2064. FB.XD._transport = 'flash';
  2065. } else {
  2066. // if we don't have fb-root, we'll fail miserably
  2067. if (FB.log) {
  2068. FB.log('missing fb-root, defaulting to fragment-based xdcomm');
  2069. }
  2070. FB.XD._transport = 'fragment';
  2071. FB.XD.Fragment._channelUrl = channelUrl || window.location.toString();
  2072. }
  2073. } else {
  2074. FB.XD._transport = 'fragment';
  2075. FB.XD.Fragment._channelUrl = channelUrl || window.location.toString();
  2076. }
  2077. var IE = !!window.attachEvent;
  2078. if (FB.XD._transport != 'postmessage' && IE && window.postMessage) {
  2079. // On IE8 and beyond, we can't use postmessage exclusively, but we *may*
  2080. // be able to use postMessage below in the handler depending on the
  2081. // 'relation' so set up for that. The deal is that we can use postmessage
  2082. // on IE that has it, but not for popups (when the relation is 'opener').
  2083. FB.XD._openerTransport = FB.XD._transport;
  2084. FB.XD._openerOrigin = FB.XD._origin;
  2085. FB.XD._nonOpenerOrigin = postMessageOrigin;
  2086. }
  2087. },
  2088. /**
  2089. * Resolve a id back to a node. An id is a string like:
  2090. * top.frames[5].frames['crazy'].parent.frames["two"].opener
  2091. *
  2092. * @param id {String} the string to resolve
  2093. * @returns {Node} the resolved window object
  2094. * @throws SyntaxError if the id is malformed
  2095. */
  2096. resolveRelation: function(id) {
  2097. var
  2098. pt,
  2099. matches,
  2100. parts = id.split('.'),
  2101. node = window;
  2102. for (var i=0, l=parts.length; i<l; i++) {
  2103. pt = parts[i];
  2104. if (pt === 'opener' || pt === 'parent' || pt === 'top') {
  2105. node = node[pt];
  2106. } else if (matches = /^frames\[['"]?([a-zA-Z0-9-_]+)['"]?\]$/.exec(pt)) {
  2107. // these regex has the `feature' of fixing some badly quoted strings
  2108. node = node.frames[matches[1]];
  2109. } else {
  2110. throw new SyntaxError('Malformed id to resolve: ' + id + ', pt: ' + pt);
  2111. }
  2112. }
  2113. return node;
  2114. },
  2115. /**
  2116. * Builds a url attached to a callback for xd messages.
  2117. *
  2118. * This is one half of the XD layer. Given a callback function, we generate
  2119. * a xd URL which will invoke the function. This allows us to generate
  2120. * redirect urls (used for next/cancel and so on) which will invoke our
  2121. * callback functions.
  2122. *
  2123. * @access private
  2124. * @param cb {Function} the callback function
  2125. * @param relation {String} parent or opener to indicate window relation
  2126. * @param forever {Boolean} indicate this handler needs to live forever
  2127. * @param id {string} Optional specified handler id
  2128. * @param force_https {Boolean} Optional param to force https
  2129. * @return {String} the xd url bound to the callback
  2130. */
  2131. handler: function(cb, relation, forever, id, force_https) {
  2132. // if for some reason, we end up trying to create a handler on a page that
  2133. // is already being used for XD comm as part of the fragment, we simply
  2134. // return 'javascript:false' to prevent a recursive page load loop
  2135. //
  2136. // the // after it makes any appended things to the url become a JS
  2137. // comment, and prevents JS parse errors. cloWntoWn.
  2138. if (window.location.toString().indexOf(FB.XD.Fragment._magic) > 0) {
  2139. return 'javascript:false;//';
  2140. }
  2141. // allow us to control force secure, which may be necessary for
  2142. // plugins for ssl-enabled users on http sites. this is because
  2143. // the facebook iframe will load this xd resource
  2144. if (FB.initSitevars.forceSecureXdProxy) {
  2145. force_https = true;
  2146. }
  2147. var xdProxy = FB.getDomain((force_https ? 'https_' : '') + 'cdn') +
  2148. FB.XD._xdProxyUrl + '#';
  2149. id = id || FB.guid();
  2150. relation = relation || 'opener';
  2151. if (FB.XD._openerTransport) {
  2152. // We're set up to swap mechanisms based on 'relation'. We don't
  2153. // worry about resetting these at the end, since we'll just set them
  2154. // again on the next invocation.
  2155. if (relation == 'opener') {
  2156. FB.XD._transport = FB.XD._openerTransport;
  2157. FB.XD._origin = FB.XD._openerOrigin;
  2158. } else {
  2159. FB.XD.PostMessage.init();
  2160. FB.XD._transport = 'postmessage';
  2161. FB.XD._origin = FB.XD._nonOpenerOrigin;
  2162. }
  2163. }
  2164. // in fragment mode, the url is the current page and a fragment with a
  2165. // magic token
  2166. if (FB.XD._transport == 'fragment') {
  2167. xdProxy = FB.XD.Fragment._channelUrl;
  2168. var poundIndex = xdProxy.indexOf('#');
  2169. if (poundIndex > 0) {
  2170. xdProxy = xdProxy.substr(0, poundIndex);
  2171. }
  2172. xdProxy += (
  2173. (xdProxy.indexOf('?') < 0 ? '?' : '&') +
  2174. FB.XD.Fragment._magic + '#?=&'
  2175. );
  2176. }
  2177. if (forever) {
  2178. FB.XD._forever[id] = true;
  2179. }
  2180. FB.XD._callbacks[id] = cb;
  2181. return xdProxy + FB.QS.encode({
  2182. cb : id,
  2183. origin : FB.XD._origin,
  2184. relation : relation,
  2185. transport : FB.XD._transport
  2186. });
  2187. },
  2188. /**
  2189. * Handles the raw or parsed message and invokes the bound callback with
  2190. * the data and removes the related window/frame.
  2191. *
  2192. * @access private
  2193. * @param data {String|Object} the message fragment string or parameters
  2194. */
  2195. recv: function(data) {
  2196. if (typeof data == 'string') {
  2197. // Try to determine if the data is in JSON format
  2198. try {
  2199. data = FB.JSON.parse(data);
  2200. } catch (e) {
  2201. // If this is not JSON, try FB.QS.decode
  2202. data = FB.QS.decode(data);
  2203. }
  2204. }
  2205. var cb = FB.XD._callbacks[data.cb];
  2206. if (!FB.XD._forever[data.cb]) {
  2207. delete FB.XD._callbacks[data.cb];
  2208. }
  2209. cb && cb(data);
  2210. },
  2211. /**
  2212. * Provides Native ``window.postMessage`` based XD support.
  2213. *
  2214. * @class FB.XD.PostMessage
  2215. * @static
  2216. * @for FB.XD
  2217. * @access private
  2218. */
  2219. PostMessage: {
  2220. _isInitialized: false,
  2221. /**
  2222. * Initialize the native PostMessage system.
  2223. *
  2224. * @access private
  2225. */
  2226. init: function() {
  2227. if (!FB.XD.PostMessage._isInitialized) {
  2228. var H = FB.XD.PostMessage.onMessage;
  2229. window.addEventListener
  2230. ? window.addEventListener('message', H, false)
  2231. : window.attachEvent('onmessage', H);
  2232. FB.XD.PostMessage._isInitialized = true;
  2233. }
  2234. },
  2235. /**
  2236. * Handles a message event.
  2237. *
  2238. * @access private
  2239. * @param event {Event} the event object
  2240. */
  2241. onMessage: function(event) {
  2242. FB.XD.recv(event.data);
  2243. }
  2244. },
  2245. /**
  2246. * Provide support for postMessage between two two webview controls
  2247. * running inside the native FB Application on mobile.
  2248. *
  2249. * @class FB.XD.WebView
  2250. * @static
  2251. * @for FB.XD
  2252. * @access private
  2253. */
  2254. WebView: {
  2255. onMessage: function(dest, origin, msg) {
  2256. FB.XD.recv(msg);
  2257. }
  2258. },
  2259. /**
  2260. * Provides Flash Local Connection based XD support.
  2261. *
  2262. * @class FB.XD.Flash
  2263. * @static
  2264. * @for FB.XD
  2265. * @access private
  2266. */
  2267. Flash: {
  2268. /**
  2269. * Initialize the Flash Local Connection.
  2270. *
  2271. * @access private
  2272. */
  2273. init: function() {
  2274. FB.Flash.onReady(function() {
  2275. document.XdComm.postMessage_init(
  2276. 'FB.XD.Flash.onMessage',
  2277. FB.XD._openerOrigin ? FB.XD._openerOrigin : FB.XD._origin);
  2278. });
  2279. },
  2280. /**
  2281. * Handles a message received by the Flash Local Connection.
  2282. *
  2283. * @access private
  2284. * @param message {String} the URI encoded string sent by the SWF
  2285. */
  2286. onMessage: function(message) {
  2287. FB.XD.recv(decodeURIComponent(message));
  2288. }
  2289. },
  2290. /**
  2291. * Provides XD support via a fragment by reusing the current page.
  2292. *
  2293. * @class FB.XD.Fragment
  2294. * @static
  2295. * @for FB.XD
  2296. * @access private
  2297. */
  2298. Fragment: {
  2299. _magic: 'fb_xd_fragment',
  2300. /**
  2301. * Check if the fragment looks like a message, and dispatch if it does.
  2302. */
  2303. checkAndDispatch: function() {
  2304. var
  2305. loc = window.location.toString(),
  2306. fragment = loc.substr(loc.indexOf('#') + 1),
  2307. magicIndex = loc.indexOf(FB.XD.Fragment._magic);
  2308. if (magicIndex > 0) {
  2309. // make these no-op to help with performance
  2310. //
  2311. // this works independent of the module being present or not, or being
  2312. // loaded before or after
  2313. FB.init = FB.getLoginStatus = FB.api = function() {};
  2314. // display none helps prevent loading of some stuff
  2315. document.documentElement.style.display = 'none';
  2316. FB.XD.resolveRelation(
  2317. FB.QS.decode(fragment).relation).FB.XD.recv(fragment);
  2318. }
  2319. }
  2320. }
  2321. });
  2322. // NOTE: self executing code.
  2323. //
  2324. // if the page is being used for fragment based XD messaging, we need to
  2325. // dispatch on load without needing any API calls. it only does stuff if the
  2326. // magic token is found in the fragment.
  2327. FB.XD.Fragment.checkAndDispatch();
  2328. /**
  2329. * Copyright Facebook Inc.
  2330. *
  2331. * Licensed under the Apache License, Version 2.0 (the "License");
  2332. * you may not use this file except in compliance with the License.
  2333. * You may obtain a copy of the License at
  2334. *
  2335. * http://www.apache.org/licenses/LICENSE-2.0
  2336. *
  2337. * Unless required by applicable law or agreed to in writing, software
  2338. * distributed under the License is distributed on an "AS IS" BASIS,
  2339. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2340. * See the License for the specific language governing permissions and
  2341. * limitations under the License.
  2342. *
  2343. * @provides fb.ua
  2344. * @layer basic
  2345. */
  2346. /**
  2347. * User Agent and OS detection. Usage is straightforward:
  2348. *
  2349. * if (FB.UA.ie()) {
  2350. * // IE
  2351. * }
  2352. *
  2353. * You can also do version checks:
  2354. *
  2355. * if (FB.UA.ie() >= 7) {
  2356. * // IE7 or better
  2357. * }
  2358. *
  2359. * The browser functions will return NaN if the browser does not match, so
  2360. * you can also do version compares the other way:
  2361. *
  2362. * if (FB.UA.ie() < 7) {
  2363. * // IE6 or worse
  2364. * }
  2365. *
  2366. * Note that the version is a float and may include a minor version number,
  2367. * so you should always use range operators to perform comparisons, not
  2368. * strict equality.
  2369. *
  2370. * **Note:** You should **strongly** prefer capability detection to browser
  2371. * version detection where it's reasonable:
  2372. *
  2373. * http://www.quirksmode.org/js/support.html
  2374. *
  2375. * Further, we have a large number of mature wrapper functions and classes
  2376. * which abstract away many browser irregularities. Check the documentation,
  2377. * grep for things, or ask on javascript@lists.facebook.com before writing yet
  2378. * another copy of "event || window.event".
  2379. *
  2380. * @task browser Determining the User Agent
  2381. * @task os Determining the User's Operating System
  2382. * @task internal Internal methods
  2383. *
  2384. * @author marcel, epriestley
  2385. */
  2386. FB.provide('UA', {
  2387. /**
  2388. * Check if the UA is Internet Explorer.
  2389. *
  2390. * @task browser
  2391. * @access public
  2392. *
  2393. * @return float|NaN Version number (if match) or NaN.
  2394. * @author marcel
  2395. */
  2396. ie: function() {
  2397. return FB.UA._populate() || this._ie;
  2398. },
  2399. /**
  2400. * Check if the UA is Firefox.
  2401. *
  2402. * @task browser
  2403. * @access public
  2404. *
  2405. * @return float|NaN Version number (if match) or NaN.
  2406. * @author marcel
  2407. */
  2408. firefox: function() {
  2409. return FB.UA._populate() || this._firefox;
  2410. },
  2411. /**
  2412. * Check if the UA is Opera.
  2413. *
  2414. * @task browser
  2415. * @access public
  2416. *
  2417. * @return float|NaN Version number (if match) or NaN.
  2418. * @author marcel
  2419. */
  2420. opera: function() {
  2421. return FB.UA._populate() || this._opera;
  2422. },
  2423. /**
  2424. * Check if the UA is Safari.
  2425. *
  2426. * @task browser
  2427. * @access public
  2428. *
  2429. * @return float|NaN Version number (if match) or NaN.
  2430. * @author marcel
  2431. */
  2432. safari: function() {
  2433. return FB.UA._populate() || this._safari;
  2434. },
  2435. /**
  2436. * Check if the UA is a Chrome browser.
  2437. *
  2438. * @task browser
  2439. * @access public
  2440. *
  2441. * @return float|NaN Version number (if match) or NaN.
  2442. * @author cjiang
  2443. */
  2444. chrome : function() {
  2445. return FB.UA._populate() || this._chrome;
  2446. },
  2447. /**
  2448. * Check if the user is running Windows.
  2449. *
  2450. * @task os
  2451. * @return bool `true' if the user's OS is Windows.
  2452. * @author marcel
  2453. */
  2454. windows: function() {
  2455. return FB.UA._populate() || this._windows;
  2456. },
  2457. /**
  2458. * Check if the user is running Mac OS X.
  2459. *
  2460. * @task os
  2461. * @return bool `true' if the user's OS is Mac OS X.
  2462. * @author marcel
  2463. */
  2464. osx: function() {
  2465. return FB.UA._populate() || this._osx;
  2466. },
  2467. /**
  2468. * Check if the user is running Linux.
  2469. *
  2470. * @task os
  2471. * @return bool `true' if the user's OS is some flavor of Linux.
  2472. * @author putnam
  2473. */
  2474. linux: function() {
  2475. return FB.UA._populate() || this._linux;
  2476. },
  2477. /**
  2478. * Check if the user is running on an iOS platform.
  2479. *
  2480. * @task os
  2481. * @return bool `true' if the user is running some flavor of the
  2482. * ios OS.
  2483. * @author beng
  2484. */
  2485. ios: function() {
  2486. FB.UA._populate();
  2487. return FB.UA.mobile() && this._ios;
  2488. },
  2489. /**
  2490. * Check if the browser is running inside a smart mobile phone.
  2491. * @return bool
  2492. * @access public
  2493. */
  2494. mobile: function() {
  2495. FB.UA._populate();
  2496. return !FB._inCanvas && this._mobile;
  2497. },
  2498. /**
  2499. * Checking if we are inside a webview of the FB App for mobile
  2500. * @return bool
  2501. * @access public
  2502. */
  2503. nativeApp: function() {
  2504. // Now native FB app generates UA like this:
  2505. //
  2506. // Mozilla/5.0 (iPhone Simulator; U; CPU iPhone OS 4_2 like Mac OS X; en_IE)
  2507. // AppleWebKit (KHTML, like Gecko) Mobile
  2508. // [FBAN/FBForIPhone;FBAV/3.5a;FBBV/3500;FBDV/i386;FBMD/
  2509. // iPhone Simulator;FBSN/iPhone OS;FBSV/4.2;FBSS/1;FBCR/;
  2510. // FBID/phone;FBLC/en_IE]
  2511. //
  2512. // We will detect by searching for FBAN/\w+;
  2513. //
  2514. return FB.UA.mobile() && navigator.userAgent.match(/FBAN\/\w+;/i);
  2515. },
  2516. /**
  2517. * Check for the Android browser.
  2518. * @return bool
  2519. * @access public
  2520. */
  2521. android: function() {
  2522. FB.UA._populate();
  2523. return FB.UA.mobile() && this._android;
  2524. },
  2525. /**
  2526. * Check for the iPad
  2527. * @return bool
  2528. * @access public
  2529. */
  2530. iPad: function() {
  2531. FB.UA._populate();
  2532. return FB.UA.mobile() && this._iPad;
  2533. },
  2534. _populated : false,
  2535. /**
  2536. * Populate the UA and OS information.
  2537. *
  2538. * @access public
  2539. * @task internal
  2540. *
  2541. * @return void
  2542. *
  2543. * @author marcel
  2544. */
  2545. _populate : function() {
  2546. if (FB.UA._populated) {
  2547. return;
  2548. }
  2549. FB.UA._populated = true;
  2550. // To work around buggy JS libraries that can't handle multi-digit
  2551. // version numbers, Opera 10's user agent string claims it's Opera
  2552. // 9, then later includes a Version/X.Y field:
  2553. //
  2554. // Opera/9.80 (foo) Presto/2.2.15 Version/10.10
  2555. // Note: if agent regex is updated, update it in xd_proxy.phpt also!
  2556. var agent = /(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))/.exec(navigator.userAgent);
  2557. var os = /(Mac OS X)|(Windows)|(Linux)/.exec(navigator.userAgent);
  2558. var ios = /\b(iPhone|iP[ao]d)/.exec(navigator.userAgent);
  2559. FB.UA._iPad = /\b(iPad)/.exec(navigator.userAgent);
  2560. FB.UA._android = navigator.userAgent.match(/Android/i);
  2561. FB.UA._mobile = ios || FB.UA._android ||
  2562. navigator.userAgent.match(/Mobile/i);
  2563. if (agent) {
  2564. FB.UA._ie = agent[1] ? parseFloat(agent[1]) : NaN;
  2565. // marcel: IE8 running in IE7 mode.
  2566. if (FB.UA._ie >= 8 && !window.HTMLCollection) {
  2567. FB.UA._ie = 7;
  2568. }
  2569. FB.UA._firefox = agent[2] ? parseFloat(agent[2]) : NaN;
  2570. FB.UA._opera = agent[3] ? parseFloat(agent[3]) : NaN;
  2571. FB.UA._safari = agent[4] ? parseFloat(agent[4]) : NaN;
  2572. if (FB.UA._safari) {
  2573. // We do not add the regexp to the above test, because it will always
  2574. // match 'safari' only since 'AppleWebKit' appears before 'Chrome' in
  2575. // the userAgent string.
  2576. agent = /(?:Chrome\/(\d+\.\d+))/.exec(navigator.userAgent);
  2577. FB.UA._chrome = agent && agent[1] ? parseFloat(agent[1]) : NaN;
  2578. } else {
  2579. FB.UA._chrome = NaN;
  2580. }
  2581. } else {
  2582. FB.UA._ie =
  2583. FB.UA._firefox =
  2584. FB.UA._opera =
  2585. FB.UA._chrome =
  2586. FB.UA._safari = NaN;
  2587. }
  2588. if (os) {
  2589. FB.UA._osx = !!os[1];
  2590. FB.UA._windows = !!os[2];
  2591. FB.UA._linux = !!os[3];
  2592. } else {
  2593. FB.UA._osx =
  2594. FB.UA._windows =
  2595. FB.UA._linux = false;
  2596. }
  2597. FB.UA._ios = ios;
  2598. }
  2599. });
  2600. /**
  2601. * Copyright Facebook Inc.
  2602. *
  2603. * Licensed under the Apache License, Version 2.0 (the "License");
  2604. * you may not use this file except in compliance with the License.
  2605. * You may obtain a copy of the License at
  2606. *
  2607. * http://www.apache.org/licenses/LICENSE-2.0
  2608. *
  2609. * Unless required by applicable law or agreed to in writing, software
  2610. * distributed under the License is distributed on an "AS IS" BASIS,
  2611. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2612. * See the License for the specific language governing permissions and
  2613. * limitations under the License.
  2614. *
  2615. *
  2616. *
  2617. * @provides fb.arbiter
  2618. * @requires fb.prelude
  2619. * fb.array
  2620. * fb.canvas
  2621. * fb.content
  2622. * fb.json
  2623. * fb.qs
  2624. * fb.xd
  2625. * fb.ua
  2626. */
  2627. /**
  2628. * Calls Arbiter in the parent Facebook window from inside an iframe
  2629. *
  2630. * @class FB.Arbiter
  2631. * @static
  2632. * @access private
  2633. */
  2634. FB.provide('Arbiter', {
  2635. _canvasProxyUrl: 'connect/canvas_proxy.php',
  2636. BEHAVIOR_EVENT: 'e',
  2637. BEHAVIOR_PERSISTENT: 'p',
  2638. BEHAVIOR_STATE : 's',
  2639. /**
  2640. * Sends a "Connect.Unsafe.{method}" Arbiter message to facebook using the
  2641. * most efficient transport available.
  2642. */
  2643. inform: function(method, params, relation, https, behavior) {
  2644. // TODO(naitik) only enable for iframe page tabs for now
  2645. if (FB.Canvas.isTabIframe() ||
  2646. (FB._inPlugin && window.postMessage) ||
  2647. (!FB._inCanvas && FB.UA.mobile() && window.postMessage)) {
  2648. var msg = FB.JSON.stringify({
  2649. method: method,
  2650. params: params,
  2651. behavior: behavior || FB.Arbiter.BEHAVIOR_PERSISTENT });
  2652. if (window.postMessage) { // native postMessage
  2653. FB.XD.resolveRelation(relation || 'parent').postMessage(msg, '*');
  2654. return;
  2655. } else {
  2656. try {
  2657. window.opener.postMessage(msg); // IE vbscript NIX transport
  2658. return;
  2659. } catch (e) {}
  2660. }
  2661. }
  2662. // canvas_proxy.php works by directly calling JS function on the parent
  2663. // window of current window. As such, it has to same document.domain and
  2664. // protocol (https/http). We don't have a good way to determine the
  2665. // protocol of the parent window and have to use window.referrer to
  2666. // infer it.
  2667. // Question: why should https ever be passed as a parameter?
  2668. https |= (window != window.parent &&
  2669. document.referrer.indexOf('https:') === 0);
  2670. // fallback static file based transport
  2671. var url = (
  2672. FB.getDomain((https ? 'https_' : '') + 'staticfb', true) +
  2673. FB.Arbiter._canvasProxyUrl + '#' +
  2674. FB.QS.encode({
  2675. method: method,
  2676. params: FB.JSON.stringify(params || {}),
  2677. behavior: behavior || FB.Arbiter.BEHAVIOR_PERSISTENT,
  2678. relation: relation
  2679. })
  2680. );
  2681. var root = FB.Content.appendHidden('');
  2682. FB.Content.insertIframe({
  2683. url: url,
  2684. root: root,
  2685. width: 1,
  2686. height: 1,
  2687. onload: function() {
  2688. setTimeout(function() {
  2689. root.parentNode.removeChild(root);
  2690. }, 10);
  2691. }
  2692. });
  2693. }
  2694. });
  2695. /**
  2696. * Copyright Facebook Inc.
  2697. *
  2698. * Licensed under the Apache License, Version 2.0 (the "License");
  2699. * you may not use this file except in compliance with the License.
  2700. * You may obtain a copy of the License at
  2701. *
  2702. * http://www.apache.org/licenses/LICENSE-2.0
  2703. *
  2704. * Unless required by applicable law or agreed to in writing, software
  2705. * distributed under the License is distributed on an "AS IS" BASIS,
  2706. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2707. * See the License for the specific language governing permissions and
  2708. * limitations under the License.
  2709. *
  2710. *
  2711. *
  2712. * @provides fb.canvas
  2713. * @requires fb.prelude
  2714. * fb.arbiter
  2715. * fb.array
  2716. * fb.xd
  2717. */
  2718. /**
  2719. * Things used by Canvas apps.
  2720. *
  2721. * ---------------------------------------------------------------------
  2722. * IMPORTANT NOTE: IF YOU ARE USING THESE FUNCTIONS, MAKE SURE YOU GO TO
  2723. *
  2724. * http://www.facebook.com/developers
  2725. *
  2726. * CLICK YOUR APP, CLICK EDIT SETTINGS, CLICK MIGRATIONS AND ENABLE
  2727. *
  2728. * New SDKs
  2729. * ---------------------------------------------------------------------
  2730. *
  2731. * @class FB.Canvas
  2732. * @static
  2733. * @access private
  2734. */
  2735. FB.provide('Canvas', {
  2736. /*
  2737. * The timer. We keep it around so we can shut if off
  2738. */
  2739. _timer: null,
  2740. /**
  2741. * Keeps track of the last size of each frame
  2742. */
  2743. _lastSize: {},
  2744. /**
  2745. * Keeps track of the last size and data about the parent canvas page
  2746. */
  2747. _pageInfo: {
  2748. clientWidth: 0,
  2749. clientHeight: 0,
  2750. scrollLeft: 0,
  2751. scrollTop: 0,
  2752. offsetLeft: 0,
  2753. offsetTop: 0
  2754. },
  2755. /**
  2756. * canvas iframe position within the parent page.
  2757. *
  2758. * calls appCallback with the fresh data from the parent frame
  2759. * returns data from previous call directly for semi-backwards compatibility
  2760. * with the previous API (which used polling)
  2761. * @return {Object} containing scrollLeft, scrollTop,
  2762. * clientWidth, clientHeight, offsetLeft, and offsetTop
  2763. *
  2764. */
  2765. getPageInfo: function(appCallback) {
  2766. var relation = 'top.frames[' + window.name + ']';
  2767. var channelUrl = FB.XD.handler(function(data) {
  2768. for (var i in FB.Canvas._pageInfo) {
  2769. if (data[i]) {
  2770. FB.Canvas._pageInfo[i] = data[i] | 0;
  2771. }
  2772. }
  2773. appCallback && appCallback(FB.Canvas._pageInfo);
  2774. }, relation, true);
  2775. var params = {
  2776. channelUrl : channelUrl,
  2777. frame : window.name
  2778. };
  2779. FB.Arbiter.inform('getPageInfo', params, 'top');
  2780. },
  2781. /**
  2782. * Use in conjunction with the hideFlashCallback parameter to FB.init().
  2783. * Developers should use this function within their hideFlashCallback to hide
  2784. * the element as soon as possible. Since Facebook will
  2785. * call this function 200ms later, and the implementation may change, using it
  2786. * is the only way to guarantee forward compatibility.
  2787. */
  2788. hideFlashElement: function(elem) {
  2789. elem.style.visibility = 'hidden';
  2790. },
  2791. /**
  2792. * Use in conjunction with FB.Canvas.hideFlashElement.
  2793. * Developers should use this function within their hideFlashCallback to show
  2794. * the element as soon as possible. Since Facebook will
  2795. * call this function 200ms later, and the implementation may change, using it
  2796. * is the only way to guarantee forward compatibility.
  2797. */
  2798. showFlashElement: function(elem) {
  2799. elem.style.visibility = '';
  2800. },
  2801. _flashClassID: "CLSID:D27CDB6E-AE6D-11CF-96B8-444553540000",
  2802. /**
  2803. * By default, we hide any flash objects that have the wmode=default or
  2804. * wmode=window when they might occlude something. Developers can
  2805. * override this with options.hideFlashCallback.
  2806. */
  2807. _hideFlashCallback: function(params) {
  2808. var candidates = window.document.getElementsByTagName('object');
  2809. for (var i = 0; i < candidates.length; i++) {
  2810. var elem = candidates[i];
  2811. if (elem.type.toLowerCase() != "application/x-shockwave-flash" &&
  2812. elem.classid.toUpperCase() != FB.Canvas._flashClassID) {
  2813. continue;
  2814. }
  2815. var good = false;
  2816. for (var j = 0; j < elem.childNodes.length; j++) {
  2817. if (elem.childNodes[j].nodeName.toLowerCase() == "param" &&
  2818. elem.childNodes[j].name.toLowerCase() == "wmode") {
  2819. if (elem.childNodes[j].value.toLowerCase() == "opaque" ||
  2820. elem.childNodes[j].value.toLowerCase() == "transparent") {
  2821. good = true;
  2822. }
  2823. }
  2824. }
  2825. if (!good) {
  2826. var rand = Math.random();
  2827. if (rand <= 1 / 1000) {
  2828. FB.api(FB._apiKey + '/occludespopups', 'post', {});
  2829. }
  2830. if (FB.Canvas._devHideFlashCallback) {
  2831. // For each flash element, we give the application 200ms to do
  2832. // something reasonable. In this scenario we assume that the flash
  2833. // object has inherited visibility, and restore to that afterward.
  2834. var wait_ms = 200;
  2835. var devArgs = {
  2836. state : params.state,
  2837. elem : elem
  2838. };
  2839. var fnToggle = FB.bind(function(devParams) {
  2840. if (devParams.state == 'opened') {
  2841. FB.Canvas.hideFlashElement(devParams.elem);
  2842. } else {
  2843. FB.Canvas.showFlashElement(devParams.elem);
  2844. }
  2845. },
  2846. this,
  2847. devArgs);
  2848. setTimeout(fnToggle, wait_ms);
  2849. FB.Canvas._devHideFlashCallback(devArgs);
  2850. } else {
  2851. if (params.state == 'opened') {
  2852. elem._old_visibility = elem.style.visibility;
  2853. elem.style.visibility = 'hidden';
  2854. } else if (params.state == 'closed') {
  2855. elem.style.visibility = elem._old_visibility;
  2856. delete elem._old_visibility;
  2857. }
  2858. }
  2859. }
  2860. }
  2861. },
  2862. _devHideFlashCallback : null,
  2863. _setHideFlashCallback: function(callback) {
  2864. FB.Canvas._devHideFlashCallback = callback;
  2865. },
  2866. init: function() {
  2867. var view = FB.Dom.getViewportInfo();
  2868. FB.Canvas._pageInfo.clientWidth = view.width;
  2869. FB.Canvas._pageInfo.clientHeight = view.height;
  2870. FB.Canvas.getPageInfo();
  2871. var channelUrl = FB.XD.handler(
  2872. FB.Canvas._hideFlashCallback, 'top.frames[' + window.name + ']', true);
  2873. // Flash objects which are wmode=window (default) have an infinite Z-order.
  2874. // Canvas chrome needs to know so it can hide the iframe when various
  2875. // elements pop up.
  2876. FB.Arbiter.inform(
  2877. 'iframeSetupFlashHiding', {channelUrl: channelUrl});
  2878. },
  2879. /**
  2880. * Tells Facebook to resize your iframe.
  2881. *
  2882. * ## Examples
  2883. *
  2884. * Call this whenever you need a resize. This usually means, once after
  2885. * pageload, and whenever your content size changes.
  2886. *
  2887. * window.fbAsyncInit = function() {
  2888. * FB.Canvas.setSize();
  2889. * }
  2890. *
  2891. * // Do things that will sometimes call sizeChangeCallback()
  2892. *
  2893. * function sizeChangeCallback() {
  2894. * FB.Canvas.setSize();
  2895. * }
  2896. *
  2897. * It will default to the current size of the frame, but if you have a need
  2898. * to pick your own size, you can use the params array.
  2899. *
  2900. * FB.Canvas.setSize({ width: 640, height: 480 }); // Live in the past
  2901. *
  2902. * The max width is whatever you picked in your app settings, and there is no
  2903. * max height.
  2904. *
  2905. * @param {Object} params
  2906. *
  2907. * Property | Type | Description | Argument | Default
  2908. * -------- | ------- | -------------------------------- | ---------- | -------
  2909. * width | Integer | Desired width. Max is app width. | *Optional* | frame width
  2910. * height | Integer | Desired height. | *Optional* | frame height
  2911. *
  2912. * @author ptarjan
  2913. */
  2914. setSize: function(params) {
  2915. // setInterval calls its function with an integer
  2916. if (typeof params != "object") {
  2917. params = {};
  2918. }
  2919. var minShrink = 0,
  2920. minGrow = 0;
  2921. params = params || {};
  2922. if (params.width == null || params.height == null) {
  2923. params = FB.copy(params, FB.Canvas._computeContentSize());
  2924. // we add a bit of hysteresis to the height check since the values
  2925. // returned from _computeContentSize() may be slightly different
  2926. // than the size we set the IFrame to so we need to avoid getting
  2927. // into a state where the IFrame keeps resizing slightly larger
  2928. // or ping ponging in size
  2929. minShrink = 16;
  2930. minGrow = 4;
  2931. }
  2932. params = FB.copy(params, { frame: window.name || 'iframe_canvas' });
  2933. // Deep compare
  2934. if (FB.Canvas._lastSize[params.frame]) {
  2935. var oldHeight = FB.Canvas._lastSize[params.frame].height;
  2936. var dHeight = params.height - oldHeight;
  2937. if (FB.Canvas._lastSize[params.frame].width == params.width &&
  2938. (dHeight <= minGrow && dHeight >= -minShrink)) {
  2939. return false;
  2940. }
  2941. }
  2942. FB.Canvas._lastSize[params.frame] = params;
  2943. FB.Arbiter.inform('setSize', params);
  2944. return true;
  2945. },
  2946. /**
  2947. * Tells Facebook to scroll your iframe.
  2948. *
  2949. * ## Examples
  2950. *
  2951. * Call this whenever you need to scroll the contents of your iframe. This
  2952. * will cause a setScroll to be executed on the containing iframe.
  2953. *
  2954. * @param {Integer} x
  2955. * @param {Integer} y
  2956. *
  2957. * Property | Type | Description | Argument | Default
  2958. * -------- | ------- | -------------------------------- | ---------- | -------
  2959. * x | Integer | Horizontal scroll position | *Required* | None
  2960. * y | Integer | Vertical scroll position | *Required* | None
  2961. *
  2962. * @author gregschechte
  2963. */
  2964. scrollTo: function(x, y) {
  2965. FB.Arbiter.inform('scrollTo', {
  2966. frame: window.name || 'iframe_canvas',
  2967. x: x,
  2968. y: y
  2969. });
  2970. },
  2971. /**
  2972. * Starts or stops a timer which resizes your iframe every few milliseconds.
  2973. *
  2974. * ## Examples
  2975. *
  2976. * This function is useful if you know your content will change size, but you
  2977. * don't know when. There will be a slight delay, so if you know when your
  2978. * content changes size, you should call [setSize](FB.Canvas.setSize)
  2979. * yourself (and save your user's CPU cycles).
  2980. *
  2981. * window.fbAsyncInit = function() {
  2982. * FB.Canvas.setAutoGrow();
  2983. * }
  2984. *
  2985. * If you ever need to stop the timer, just pass false.
  2986. *
  2987. * FB.Canvas.setAutoGrow(false);
  2988. *
  2989. * If you want the timer to run at a different interval, you can do that too.
  2990. *
  2991. * FB.Canvas.setAutoGrow(91); // Paul's favorite number
  2992. *
  2993. * Note: If there is only 1 parameter and it is a number, it is assumed to be
  2994. * the interval.
  2995. *
  2996. * @param {Boolean} onOrOff Whether to turn the timer on or off. truthy ==
  2997. * on, falsy == off. **default** is true
  2998. * @param {Integer} interval How often to resize (in ms). **default** is
  2999. * 100ms
  3000. *
  3001. * @author ptarjan
  3002. */
  3003. setAutoGrow: function(onOrOff, interval) {
  3004. // I did this a few times, so I expect many users will too
  3005. if (interval === undefined && typeof onOrOff == "number") {
  3006. interval = onOrOff;
  3007. onOrOff = true;
  3008. }
  3009. if (onOrOff === undefined || onOrOff) {
  3010. if (FB.Canvas._timer === null) {
  3011. FB.Canvas._timer =
  3012. window.setInterval(FB.Canvas.setSize,
  3013. interval || 100); // 100 ms is the default
  3014. }
  3015. FB.Canvas.setSize();
  3016. } else {
  3017. if (FB.Canvas._timer !== null) {
  3018. window.clearInterval(FB.Canvas._timer);
  3019. FB.Canvas._timer = null;
  3020. }
  3021. }
  3022. },
  3023. /**
  3024. * @deprecated use setAutoGrow()
  3025. */
  3026. setAutoResize: function(onOrOff, interval) {
  3027. return FB.Canvas.setAutoGrow(onOrOff, interval);
  3028. },
  3029. /**
  3030. * The "app_runner_" pattern is set by facebook.com when embeding an
  3031. * application iframe(for now, only actually used on page tabs).
  3032. * If we detect this pattern, we can safely assume the
  3033. * parent frame will be able to handle async style ui calls.
  3034. * @return {Boolean} as explained above
  3035. */
  3036. isTabIframe: function() {
  3037. return (window.name.indexOf('app_runner_') === 0);
  3038. },
  3039. /**
  3040. * This method should be called when your app is finished loading to the point
  3041. * when the user can use it.
  3042. * Pass in a callback which receives a struct like so:
  3043. * { time_delta_ms: 2346 }
  3044. * Which is the number of milliseconds between the moment the full canvas
  3045. * page began executing and when you called the function.
  3046. * This information will then be logged for Facebook Insights.
  3047. */
  3048. setDoneLoading : function(callback) {
  3049. FB.Canvas._passAppTtiMessage(callback, 'RecordIframeAppTti');
  3050. },
  3051. /**
  3052. * When using FB.Canvas.setDoneLoading, this method can be called before
  3053. * periods of time that should not be measured, such as waiting for a user to
  3054. * click a button.
  3055. * Pass in a callback which receives a struct like so:
  3056. * { time_delta_ms: 2346 }
  3057. * Which is the number of milliseconds between the moment the full canvas
  3058. * page began executing and when you called the function.
  3059. */
  3060. stopTimer : function(callback) {
  3061. FB.Canvas._passAppTtiMessage(callback, 'StopIframeAppTtiTimer');
  3062. },
  3063. /**
  3064. * This method can be called to register a callback for inline processing
  3065. * of user actions, such as clicks on OG action ticker stories.
  3066. * For instance, if user uses your app and clicks on achievement action,
  3067. * you can process it without reloading the page.
  3068. *
  3069. * Each call to setUrlHandler removes previously set callback, if there
  3070. * was one.
  3071. *
  3072. * @param {Function} callback function taking one argument: an object,
  3073. * field of which will be 'path' - the path relative to app's canvas URL;
  3074. * for instance, if the URL that would have been loaded was
  3075. * http://apps.facebook.com/app/achievement1.php?fb_rel=canvas_ticker...
  3076. * then callback will get {path: "/achievement1.php?fb_rel=canvas_ti..."}
  3077. *
  3078. * ## Example
  3079. *
  3080. * function onUrl(data) {
  3081. * if(data.path.indexOf("games.achieves") != -1) {
  3082. * console.log('I will process some achievement now.');
  3083. * } else {
  3084. * window.location = data.path;
  3085. * }
  3086. * }
  3087. *
  3088. * FB.Canvas.setUrlHandler(onUrl);
  3089. */
  3090. setUrlHandler : function(callback) {
  3091. var channelUrl = FB.XD.handler(callback,
  3092. 'top.frames[' + window.name + ']',
  3093. true);
  3094. FB.Arbiter.inform('setUrlHandler', channelUrl);
  3095. FB.Event.listen(window, 'load', function() {
  3096. FB.Arbiter.inform('setUrlHandler', channelUrl);
  3097. });
  3098. },
  3099. /**
  3100. * When using FB.Canvas.setDoneLoading, this method can be called after
  3101. * periods of time that should not be measured, such as after a user clicks a
  3102. * button.
  3103. */
  3104. startTimer : function() {
  3105. FB.Canvas._passAppTtiMessage(null, 'StartIframeAppTtiTimer');
  3106. },
  3107. _passAppTtiMessage : function(callback, message_name) {
  3108. var devCallback = null;
  3109. if (callback) {
  3110. devCallback = FB.XD.handler(callback,
  3111. 'top.frames[' + window.name + ']', false);
  3112. }
  3113. FB.Arbiter.inform(message_name,
  3114. { frame: window.name || 'iframe_canvas',
  3115. time: (new Date()).getTime(),
  3116. appId: parseInt(FB._apiKey, 10),
  3117. channelUrl: devCallback
  3118. });
  3119. },
  3120. /**
  3121. * Determine the size of the actual contents of the iframe.
  3122. *
  3123. * There is no reliable way to get the height when the content is
  3124. * smaller than the IFrame in all browsers for all css.
  3125. * From measuring here's what works:
  3126. * CSS pos: default relative absolute fixed
  3127. * Webkit G+S G+S G x
  3128. * Firefox G+S G G x
  3129. * IE G G G x
  3130. *
  3131. * The only safe thing we can do is grow.
  3132. *
  3133. * Here's measured results from a test app. While it looks like we
  3134. * ought to be able to use body.offsetHeight, it turns out there are
  3135. * cases where apps with complex css are reported as much smaller
  3136. * than they actually render.
  3137. *
  3138. * content > IFrame=800
  3139. * body docElement jQuery .height()
  3140. * scroll offset scroll offset body doc
  3141. * chrome: 1838 1799 1834 1838 800 1838
  3142. * safari: 1838 1799 1834 1838 800 1838
  3143. * firefo: 1863 1863 1903 1903 800 1903
  3144. * ie7 : 2038 2038 2055 800 800 2055
  3145. * ie8 : 1850 1850 1890 800 800 1890
  3146. * ie9 : 1836 1820 1861 800 800 1861
  3147. * opera : 1850 1850 11890 1890
  3148. *
  3149. * content < IFrame=800
  3150. * body docElement jQuery .height()
  3151. * scroll offset scroll offset body doc
  3152. * chrome: 800 439 474 478 800 800
  3153. * safari: 800 439 474 478 800 800
  3154. * firefo: 455 455 798 493 800 800
  3155. * ie7 : 518 518 535 800 800 800
  3156. * ie8 : 450 450 800 800 800 800
  3157. * ie9 : 460 444 800 800 800 800
  3158. * opera : 450 450 10490 490
  3159. *
  3160. * Patches and test cases are welcome.
  3161. */
  3162. _computeContentSize: function() {
  3163. var body = document.body,
  3164. docElement = document.documentElement,
  3165. right = 0,
  3166. bodyTop = Math.max(body.offsetTop, 0),
  3167. docTop = Math.max(docElement.offsetTop, 0),
  3168. bodyScroll = body.scrollHeight + bodyTop,
  3169. bodyOffset = body.offsetHeight + bodyTop,
  3170. docScroll = docElement.scrollHeight + docTop,
  3171. docOffset = docElement.offsetHeight + docTop;
  3172. bottom = Math.max(bodyScroll, bodyOffset, docScroll, docOffset);
  3173. if (body.offsetWidth < body.scrollWidth) {
  3174. right = body.scrollWidth + body.offsetLeft;
  3175. } else {
  3176. FB.Array.forEach(body.childNodes, function(child) {
  3177. var childRight = child.offsetWidth + child.offsetLeft;
  3178. if (childRight > right) {
  3179. right = childRight;
  3180. }
  3181. });
  3182. }
  3183. if (docElement.clientLeft > 0) {
  3184. right += (docElement.clientLeft * 2);
  3185. }
  3186. if (docElement.clientTop > 0) {
  3187. bottom += (docElement.clientTop * 2);
  3188. }
  3189. return {height: bottom, width: right};
  3190. }
  3191. });
  3192. /**
  3193. * Copyright Facebook Inc.
  3194. *
  3195. * Licensed under the Apache License, Version 2.0 (the "License");
  3196. * you may not use this file except in compliance with the License.
  3197. * You may obtain a copy of the License at
  3198. *
  3199. * http://www.apache.org/licenses/LICENSE-2.0
  3200. *
  3201. * Unless required by applicable law or agreed to in writing, software
  3202. * distributed under the License is distributed on an "AS IS" BASIS,
  3203. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3204. * See the License for the specific language governing permissions and
  3205. * limitations under the License.
  3206. *
  3207. * @provides fb.string
  3208. * @layer basic
  3209. * @requires fb.prelude
  3210. *
  3211. */
  3212. /**
  3213. * Utility function related to Strings.
  3214. *
  3215. * @class FB.String
  3216. * @static
  3217. * @private
  3218. */
  3219. FB.provide('String', {
  3220. /**
  3221. * Strip leading and trailing whitespace.
  3222. *
  3223. * @param s {String} the string to trim
  3224. * @returns {String} the trimmed string
  3225. */
  3226. trim: function(s) {
  3227. return s.replace(/^\s*|\s*$/g, '');
  3228. },
  3229. /**
  3230. * Format a string.
  3231. *
  3232. * Example:
  3233. * FB.String.format('{0}.facebook.com/{1}', 'www', 'login.php')
  3234. * Returns:
  3235. * 'www.facebook.com/login.php'
  3236. *
  3237. * Example:
  3238. * FB.String.format('foo {0}, {1}, {0}', 'x', 'y')
  3239. * Returns:
  3240. * 'foo x, y, x'
  3241. *
  3242. * @static
  3243. * @param format {String} the format specifier
  3244. * @param arguments {...} placeholder arguments
  3245. * @returns {String} the formatted string
  3246. */
  3247. format: function(format) {
  3248. if (!FB.String.format._formatRE) {
  3249. FB.String.format._formatRE = /(\{[^\}^\{]+\})/g;
  3250. }
  3251. var values = arguments;
  3252. return format.replace(
  3253. FB.String.format._formatRE,
  3254. function(str, m) {
  3255. var
  3256. index = parseInt(m.substr(1), 10),
  3257. value = values[index + 1];
  3258. if (value === null || value === undefined) {
  3259. return '';
  3260. }
  3261. return value.toString();
  3262. }
  3263. );
  3264. },
  3265. /**
  3266. * Escape a string to safely use it as HTML.
  3267. *
  3268. * @param value {String} string to escape
  3269. * @return {String} the escaped string
  3270. */
  3271. escapeHTML: function(value) {
  3272. var div = document.createElement('div');
  3273. div.appendChild(document.createTextNode(value));
  3274. return div.innerHTML.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
  3275. },
  3276. /**
  3277. * Escape a string so that it can be embedded inside another string
  3278. * as quoted string.
  3279. *
  3280. * @param value {String} string to quote
  3281. * @return {String} the quoted string
  3282. */
  3283. quote: function(value) {
  3284. var
  3285. quotes = /["\\\x00-\x1f\x7f-\x9f]/g,
  3286. subst = { // table of character substitutions
  3287. '\b': '\\b',
  3288. '\t': '\\t',
  3289. '\n': '\\n',
  3290. '\f': '\\f',
  3291. '\r': '\\r',
  3292. '"' : '\\"',
  3293. '\\': '\\\\'
  3294. };
  3295. return quotes.test(value) ?
  3296. '"' + value.replace(quotes, function (a) {
  3297. var c = subst[a];
  3298. if (c) {
  3299. return c;
  3300. }
  3301. c = a.charCodeAt();
  3302. return '\\u00' + Math.floor(c/16).toString(16) + (c % 16).toString(16);
  3303. }) + '"' :
  3304. '"' + value + '"';
  3305. }
  3306. });
  3307. /**
  3308. * Copyright Facebook Inc.
  3309. *
  3310. * Licensed under the Apache License, Version 2.0 (the "License");
  3311. * you may not use this file except in compliance with the License.
  3312. * You may obtain a copy of the License at
  3313. *
  3314. * http://www.apache.org/licenses/LICENSE-2.0
  3315. *
  3316. * Unless required by applicable law or agreed to in writing, software
  3317. * distributed under the License is distributed on an "AS IS" BASIS,
  3318. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3319. * See the License for the specific language governing permissions and
  3320. * limitations under the License.
  3321. *
  3322. * @provides fb.dom
  3323. * @layer basic
  3324. * @requires fb.prelude
  3325. * fb.event
  3326. * fb.string
  3327. * fb.array
  3328. * fb.ua
  3329. */
  3330. /**
  3331. * This provides helper methods related to DOM.
  3332. *
  3333. * @class FB.Dom
  3334. * @static
  3335. * @private
  3336. */
  3337. FB.provide('Dom', {
  3338. /**
  3339. * Check if the element contains a class name.
  3340. *
  3341. * @param dom {DOMElement} the element
  3342. * @param className {String} the class name
  3343. * @return {Boolean}
  3344. */
  3345. containsCss: function(dom, className) {
  3346. var cssClassWithSpace = ' ' + dom.className + ' ';
  3347. return cssClassWithSpace.indexOf(' ' + className + ' ') >= 0;
  3348. },
  3349. /**
  3350. * Add a class to a element.
  3351. *
  3352. * @param dom {DOMElement} the element
  3353. * @param className {String} the class name
  3354. */
  3355. addCss: function(dom, className) {
  3356. if (!FB.Dom.containsCss(dom, className)) {
  3357. dom.className = dom.className + ' ' + className;
  3358. }
  3359. },
  3360. /**
  3361. * Remove a class from the element.
  3362. *
  3363. * @param dom {DOMElement} the element
  3364. * @param className {String} the class name
  3365. */
  3366. removeCss: function(dom, className) {
  3367. if (FB.Dom.containsCss(dom, className)) {
  3368. dom.className = dom.className.replace(className, '');
  3369. FB.Dom.removeCss(dom, className); // in case of repetition
  3370. }
  3371. },
  3372. /**
  3373. * Finds elements that have a certain class name
  3374. * A wrapper around document.querySelectorAll if
  3375. * supported, otherwise loops through all dom elements of given tagName
  3376. * hunting for the className.
  3377. *
  3378. * @param {String} className Class name we're interested in
  3379. * @param {HTMLElement} dom (optional) Element to search in
  3380. * @param {String} tagName (optional) Type of tag to look for, default "*"
  3381. * @return {Array}
  3382. */
  3383. getByClass: function(className, dom, tagName) {
  3384. dom = dom || document.body;
  3385. tagName = tagName || '*';
  3386. if (dom.querySelectorAll) {
  3387. return FB.Array.toArray(
  3388. dom.querySelectorAll(tagName + '.' + className)
  3389. );
  3390. }
  3391. var all = dom.getElementsByTagName(tagName),
  3392. els = [];
  3393. for (var i = 0, len = all.length; i < len; i++) {
  3394. if (this.containsCss(all[i], className)) {
  3395. els[els.length] = all[i];
  3396. }
  3397. }
  3398. return els;
  3399. },
  3400. /**
  3401. * Returns the computed style for the element
  3402. *
  3403. * note: requires browser specific names to be passed for specials
  3404. * border-radius -> ('-moz-border-radius', 'border-radius')
  3405. *
  3406. * @param dom {DOMElement} the element
  3407. * @param styleProp {String} the property name
  3408. */
  3409. getStyle: function (dom, styleProp) {
  3410. var y = false, s = dom.style;
  3411. if (dom.currentStyle) { // camelCase (e.g. 'marginTop')
  3412. FB.Array.forEach(styleProp.match(/\-([a-z])/g), function(match) {
  3413. styleProp = styleProp.replace(match, match.substr(1,1).toUpperCase());
  3414. });
  3415. y = dom.currentStyle[styleProp];
  3416. } else { // dashes (e.g. 'margin-top')
  3417. FB.Array.forEach(styleProp.match(/[A-Z]/g), function(match) {
  3418. styleProp = styleProp.replace(match, '-'+ match.toLowerCase());
  3419. });
  3420. if (window.getComputedStyle) {
  3421. y = document.defaultView
  3422. .getComputedStyle(dom,null).getPropertyValue(styleProp);
  3423. // special handling for IE
  3424. // for some reason it doesn't return '0%' for defaults. so needed to
  3425. // translate 'top' and 'left' into '0px'
  3426. if (styleProp == 'background-position-y' ||
  3427. styleProp == 'background-position-x') {
  3428. if (y == 'top' || y == 'left') { y = '0px'; }
  3429. }
  3430. }
  3431. }
  3432. if (styleProp == 'opacity') {
  3433. if (dom.filters && dom.filters.alpha) {
  3434. return y;
  3435. }
  3436. return y * 100;
  3437. }
  3438. return y;
  3439. },
  3440. /**
  3441. * Sets the style for the element to value
  3442. *
  3443. * note: requires browser specific names to be passed for specials
  3444. * border-radius -> ('-moz-border-radius', 'border-radius')
  3445. *
  3446. * @param dom {DOMElement} the element
  3447. * @param styleProp {String} the property name
  3448. * @param value {String} the css value to set this property to
  3449. */
  3450. setStyle: function(dom, styleProp, value) {
  3451. var s = dom.style;
  3452. if (styleProp == 'opacity') {
  3453. if (value >= 100) { value = 99.999; } // fix for Mozilla < 1.5b2
  3454. if (value < 0) { value = 0; }
  3455. s.opacity = value/100;
  3456. s.MozOpacity = value/100;
  3457. s.KhtmlOpacity = value/100;
  3458. if (dom.filters) {
  3459. if (dom.filters.alpha == undefined) {
  3460. dom.filter = "alpha(opacity=" + value + ")";
  3461. } else {
  3462. dom.filters.alpha.opacity = value;
  3463. }
  3464. }
  3465. } else { s[styleProp] = value; }
  3466. },
  3467. /**
  3468. * Dynamically add a script tag.
  3469. *
  3470. * @param src {String} the url for the script
  3471. */
  3472. addScript: function(src) {
  3473. var script = document.createElement('script');
  3474. script.type = "text/javascript";
  3475. script.src = src;
  3476. return document.getElementsByTagName('head')[0].appendChild(script);
  3477. },
  3478. /**
  3479. * Add CSS rules using a <style> tag.
  3480. *
  3481. * @param styles {String} the styles
  3482. * @param names {Array} the component names that the styles represent
  3483. */
  3484. addCssRules: function(styles, names) {
  3485. if (!FB.Dom._cssRules) {
  3486. FB.Dom._cssRules = {};
  3487. }
  3488. // note, we potentially re-include CSS if it comes with other CSS that we
  3489. // have previously not included.
  3490. var allIncluded = true;
  3491. FB.Array.forEach(names, function(id) {
  3492. if (!(id in FB.Dom._cssRules)) {
  3493. allIncluded = false;
  3494. FB.Dom._cssRules[id] = true;
  3495. }
  3496. });
  3497. if (allIncluded) {
  3498. return;
  3499. }
  3500. //#JSCOVERAGE_IF
  3501. if (!FB.UA.ie()) {
  3502. var style = document.createElement('style');
  3503. style.type = 'text/css';
  3504. style.textContent = styles;
  3505. document.getElementsByTagName('head')[0].appendChild(style);
  3506. } else {
  3507. try {
  3508. document.createStyleSheet().cssText = styles;
  3509. } catch (exc) {
  3510. // major problem on IE : You can only create 31 stylesheet objects with
  3511. // this method. We will have to add the styles into an existing
  3512. // stylesheet.
  3513. if (document.styleSheets[0]) {
  3514. document.styleSheets[0].cssText += styles;
  3515. }
  3516. }
  3517. }
  3518. },
  3519. /**
  3520. * Get the viewport info. Contains size and scroll offsets.
  3521. *
  3522. * @returns {Object} with the width and height
  3523. */
  3524. getViewportInfo: function() {
  3525. // W3C compliant, or fallback to body
  3526. var root = (document.documentElement && document.compatMode == 'CSS1Compat')
  3527. ? document.documentElement
  3528. : document.body;
  3529. return {
  3530. scrollTop : root.scrollTop,
  3531. scrollLeft : root.scrollLeft,
  3532. width : self.innerWidth ? self.innerWidth : root.clientWidth,
  3533. height : self.innerHeight ? self.innerHeight : root.clientHeight
  3534. };
  3535. },
  3536. /**
  3537. * Bind a function to be executed when the DOM is ready. It will be executed
  3538. * immediately if the DOM is already ready.
  3539. *
  3540. * @param {Function} the function to invoke when ready
  3541. */
  3542. ready: function(fn) {
  3543. if (FB.Dom._isReady) {
  3544. fn && fn();
  3545. } else {
  3546. FB.Event.subscribe('dom.ready', fn);
  3547. }
  3548. },
  3549. /**
  3550. * Find where `node` is on the page
  3551. *
  3552. * @param {DOMElement} the element
  3553. * @return {Object} with properties x and y
  3554. */
  3555. getPosition: function(node) {
  3556. var x = 0,
  3557. y = 0;
  3558. do {
  3559. x += node.offsetLeft;
  3560. y += node.offsetTop;
  3561. } while (node = node.offsetParent);
  3562. return {x: x, y: y};
  3563. }
  3564. });
  3565. // NOTE: This code is self-executing. This is necessary in order to correctly
  3566. // determine the ready status.
  3567. (function() {
  3568. // Handle when the DOM is ready
  3569. function domReady() {
  3570. FB.Dom._isReady = true;
  3571. FB.Event.fire('dom.ready');
  3572. FB.Event.clear('dom.ready');
  3573. }
  3574. // In case we're already ready.
  3575. if (FB.Dom._isReady || document.readyState == 'complete') {
  3576. return domReady();
  3577. }
  3578. // Good citizens.
  3579. if (document.addEventListener) {
  3580. document.addEventListener('DOMContentLoaded', domReady, false);
  3581. // Bad citizens.
  3582. } else if (document.attachEvent) {
  3583. document.attachEvent('onreadystatechange', domReady);
  3584. }
  3585. // Bad citizens.
  3586. // If IE is used and page is not in a frame, continuously check to see if
  3587. // the document is ready
  3588. if (FB.UA.ie() && window === top) {
  3589. (function() {
  3590. try {
  3591. // If IE is used, use the trick by Diego Perini
  3592. // http://javascript.nwbox.com/IEContentLoaded/
  3593. document.documentElement.doScroll('left');
  3594. } catch(error) {
  3595. setTimeout(arguments.callee, 0);
  3596. return;
  3597. }
  3598. // and execute any waiting functions
  3599. domReady();
  3600. })();
  3601. }
  3602. // Ultimate Fallback.
  3603. var oldonload = window.onload;
  3604. window.onload = function() {
  3605. domReady();
  3606. if (oldonload) {
  3607. if (typeof oldonload == 'string') {
  3608. eval(oldonload);
  3609. } else {
  3610. oldonload();
  3611. }
  3612. }
  3613. };
  3614. })();
  3615. /**
  3616. * Copyright Facebook Inc.
  3617. *
  3618. * Licensed under the Apache License, Version 2.0 (the "License");
  3619. * you may not use this file except in compliance with the License.
  3620. * You may obtain a copy of the License at
  3621. *
  3622. * http://www.apache.org/licenses/LICENSE-2.0
  3623. *
  3624. * Unless required by applicable law or agreed to in writing, software
  3625. * distributed under the License is distributed on an "AS IS" BASIS,
  3626. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3627. * See the License for the specific language governing permissions and
  3628. * limitations under the License.
  3629. *
  3630. * @provides fb.intl
  3631. * @requires fb.prelude
  3632. */
  3633. /**
  3634. * Provides i18n machinery.
  3635. *
  3636. * @class FB.Intl
  3637. * @static
  3638. * @access private
  3639. */
  3640. FB.provide('Intl', (function() {
  3641. /**
  3642. * Regular expression snippet containing all the characters that we
  3643. * count as sentence-final punctuation.
  3644. */
  3645. var _punctCharClass = (
  3646. '[' +
  3647. '.!?' +
  3648. '\u3002' + // Chinese/Japanese period
  3649. '\uFF01' + // Fullwidth exclamation point
  3650. '\uFF1F' + // Fullwidth question mark
  3651. '\u0964' + // Hindi "full stop"
  3652. '\u2026' + // Chinese ellipsis
  3653. '\u0EAF' + // Laotian ellipsis
  3654. '\u1801' + // Mongolian ellipsis
  3655. '\u0E2F' + // Thai ellipsis
  3656. '\uFF0E' + // Fullwidth full stop
  3657. ']'
  3658. );
  3659. /**
  3660. * Checks whether a string ends in sentence-final punctuation. This logic is
  3661. * about the same as the PHP ends_in_punct() function; it takes into account
  3662. * the fact that we consider a string like "foo." to end with a period even
  3663. * though there's a quote mark afterward.
  3664. */
  3665. function _endsInPunct(str) {
  3666. if (typeof str != 'string') {
  3667. return false;
  3668. }
  3669. return str.match(new RegExp(
  3670. _punctCharClass +
  3671. '[' +
  3672. ')"' +
  3673. "'" +
  3674. // JavaScript doesn't support Unicode character
  3675. // properties in regexes, so we have to list
  3676. // all of these individually. This is an
  3677. // abbreviated list of the "final punctuation"
  3678. // and "close punctuation" Unicode codepoints,
  3679. // excluding symbols we're unlikely to ever
  3680. // see (mathematical notation, etc.)
  3681. '\u00BB' + // Double angle quote
  3682. '\u0F3B' + // Tibetan close quote
  3683. '\u0F3D' + // Tibetan right paren
  3684. '\u2019' + // Right single quote
  3685. '\u201D' + // Right double quote
  3686. '\u203A' + // Single right angle quote
  3687. '\u3009' + // Right angle bracket
  3688. '\u300B' + // Right double angle bracket
  3689. '\u300D' + // Right corner bracket
  3690. '\u300F' + // Right hollow corner bracket
  3691. '\u3011' + // Right lenticular bracket
  3692. '\u3015' + // Right tortoise shell bracket
  3693. '\u3017' + // Right hollow lenticular bracket
  3694. '\u3019' + // Right hollow tortoise shell
  3695. '\u301B' + // Right hollow square bracket
  3696. '\u301E' + // Double prime quote
  3697. '\u301F' + // Low double prime quote
  3698. '\uFD3F' + // Ornate right parenthesis
  3699. '\uFF07' + // Fullwidth apostrophe
  3700. '\uFF09' + // Fullwidth right parenthesis
  3701. '\uFF3D' + // Fullwidth right square bracket
  3702. '\s' +
  3703. ']*$'
  3704. ));
  3705. }
  3706. /**
  3707. * i18n string formatting
  3708. *
  3709. * @param str {String} the string id
  3710. * @param args {Object} the replacement tokens
  3711. */
  3712. function _substituteTokens(str, args) {
  3713. // Does the token substitution for tx() but without the string lookup.
  3714. // Used for in-place substitutions in translation mode.
  3715. if (args !== undefined) {
  3716. if (typeof args != 'object') {
  3717. FB.log(
  3718. 'The second arg to FB.Intl.tx() must be an Object for ' +
  3719. 'FB.Intl.tx(' + str + ', ...)'
  3720. );
  3721. } else {
  3722. var regexp;
  3723. for (var key in args) {
  3724. if (args.hasOwnProperty(key)) {
  3725. // _substituteTokens("You are a {what}.", {what:'cow!'}) should be
  3726. // "You are a cow!" rather than "You are a cow!."
  3727. if (_endsInPunct(args[key])) {
  3728. // Replace both the token and the sentence-final punctuation
  3729. // after it, if any.
  3730. regexp = new RegExp('\{' + key + '\}' +
  3731. _punctCharClass + '*',
  3732. 'g');
  3733. } else {
  3734. regexp = new RegExp('\{' + key + '\}', 'g');
  3735. }
  3736. str = str.replace(regexp, args[key]);
  3737. }
  3738. }
  3739. }
  3740. }
  3741. return str;
  3742. }
  3743. /**
  3744. * i18n string formatting
  3745. *
  3746. * @access private
  3747. * @param str {String} the string id
  3748. * @param args {Object} the replacement tokens
  3749. */
  3750. function tx(str, args) {
  3751. // Fail silently if the string table isn't defined. This behaviour is used
  3752. // when a developer chooses the host the library themselves, rather than
  3753. // using the one served from facebook.
  3754. if (!FB.Intl._stringTable) {
  3755. return null;
  3756. }
  3757. return _substituteTokens(FB.Intl._stringTable[str], args);
  3758. }
  3759. // FB.Intl.tx('key') is rewritten to FB.Intl.tx._('Translated value')
  3760. tx._ = _substituteTokens;
  3761. return {
  3762. tx: tx,
  3763. // Temporary, for push safety. We are renaming _tx to tx._, and need this
  3764. // to allow users with the new JS to hit old servers.
  3765. _tx: _substituteTokens
  3766. };
  3767. })());
  3768. /**
  3769. * Copyright Facebook Inc.
  3770. *
  3771. * Licensed under the Apache License, Version 2.0 (the "License");
  3772. * you may not use this file except in compliance with the License.
  3773. * You may obtain a copy of the License at
  3774. *
  3775. * http://www.apache.org/licenses/LICENSE-2.0
  3776. *
  3777. * Unless required by applicable law or agreed to in writing, software
  3778. * distributed under the License is distributed on an "AS IS" BASIS,
  3779. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3780. * See the License for the specific language governing permissions and
  3781. * limitations under the License.
  3782. *
  3783. * @provides fb.type
  3784. * @layer basic
  3785. * @requires fb.prelude
  3786. */
  3787. // Provide Class/Type support.
  3788. // TODO: As a temporary hack, this docblock is written as if it describes the
  3789. // top level FB namespace. This is necessary because the current documentation
  3790. // parser uses the description from this file for some reason.
  3791. /**
  3792. * The top level namespace exposed by the SDK. Look at the [readme on
  3793. * **GitHub**][readme] for more information.
  3794. *
  3795. * [readme]: http://github.com/facebook/connect-js
  3796. *
  3797. * @class FB
  3798. * @static
  3799. */
  3800. FB.provide('', {
  3801. /**
  3802. * Bind a function to a given context and arguments.
  3803. *
  3804. * @static
  3805. * @access private
  3806. * @param fn {Function} the function to bind
  3807. * @param context {Object} object used as context for function execution
  3808. * @param {...} arguments additional arguments to be bound to the function
  3809. * @returns {Function} the bound function
  3810. */
  3811. bind: function() {
  3812. var
  3813. args = Array.prototype.slice.call(arguments),
  3814. fn = args.shift(),
  3815. context = args.shift();
  3816. return function() {
  3817. return fn.apply(
  3818. context,
  3819. args.concat(Array.prototype.slice.call(arguments))
  3820. );
  3821. };
  3822. },
  3823. /**
  3824. * Create a new class.
  3825. *
  3826. * Note: I have to use 'Class' instead of 'class' because 'class' is
  3827. * a reserved (but unused) keyword.
  3828. *
  3829. * @access private
  3830. * @param name {string} class name
  3831. * @param constructor {function} class constructor
  3832. * @param proto {object} instance methods for class
  3833. */
  3834. Class: function(name, constructor, proto) {
  3835. if (FB.CLASSES[name]) {
  3836. return FB.CLASSES[name];
  3837. }
  3838. var newClass = constructor || function() {};
  3839. newClass.prototype = proto;
  3840. newClass.prototype.bind = function(fn) {
  3841. return FB.bind(fn, this);
  3842. };
  3843. newClass.prototype.constructor = newClass;
  3844. FB.create(name, newClass);
  3845. FB.CLASSES[name] = newClass;
  3846. return newClass;
  3847. },
  3848. /**
  3849. * Create a subclass
  3850. *
  3851. * Note: To call base class constructor, use this._base(...).
  3852. * If you override a method 'foo' but still want to call
  3853. * the base class's method 'foo', use this._callBase('foo', ...)
  3854. *
  3855. * @access private
  3856. * @param {string} name class name
  3857. * @param {string} baseName,
  3858. * @param {function} constructor class constructor
  3859. * @param {object} proto instance methods for class
  3860. */
  3861. subclass: function(name, baseName, constructor, proto) {
  3862. if (FB.CLASSES[name]) {
  3863. return FB.CLASSES[name];
  3864. }
  3865. var base = FB.create(baseName);
  3866. FB.copy(proto, base.prototype);
  3867. proto._base = base;
  3868. proto._callBase = function(method) {
  3869. var args = Array.prototype.slice.call(arguments, 1);
  3870. return base.prototype[method].apply(this, args);
  3871. };
  3872. return FB.Class(
  3873. name,
  3874. constructor ? constructor : function() {
  3875. if (base.apply) {
  3876. base.apply(this, arguments);
  3877. }
  3878. },
  3879. proto
  3880. );
  3881. },
  3882. CLASSES: {}
  3883. });
  3884. /**
  3885. * @class FB.Type
  3886. * @static
  3887. * @private
  3888. */
  3889. FB.provide('Type', {
  3890. isType: function(obj, type) {
  3891. while (obj) {
  3892. if (obj.constructor === type || obj === type) {
  3893. return true;
  3894. } else {
  3895. obj = obj._base;
  3896. }
  3897. }
  3898. return false;
  3899. }
  3900. });
  3901. /**
  3902. * Copyright Facebook Inc.
  3903. *
  3904. * Licensed under the Apache License, Version 2.0 (the "License");
  3905. * you may not use this file except in compliance with the License.
  3906. * You may obtain a copy of the License at
  3907. *
  3908. * http://www.apache.org/licenses/LICENSE-2.0
  3909. *
  3910. * Unless required by applicable law or agreed to in writing, software
  3911. * distributed under the License is distributed on an "AS IS" BASIS,
  3912. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3913. * See the License for the specific language governing permissions and
  3914. * limitations under the License.
  3915. *
  3916. * @provides fb.obj
  3917. * @requires fb.type
  3918. * fb.json
  3919. * fb.event
  3920. */
  3921. /**
  3922. * Base object type that support events.
  3923. *
  3924. * @class FB.Obj
  3925. * @private
  3926. */
  3927. FB.Class('Obj', null,
  3928. FB.copy({
  3929. /**
  3930. * Set property on an object and fire property changed event if changed.
  3931. *
  3932. * @param {String} Property name. A event with the same name
  3933. * will be fire when the property is changed.
  3934. * @param {Object} new value of the property
  3935. * @private
  3936. */
  3937. setProperty: function(name, value) {
  3938. // Check if property actually changed
  3939. if (FB.JSON.stringify(value) != FB.JSON.stringify(this[name])) {
  3940. this[name] = value;
  3941. this.fire(name, value);
  3942. }
  3943. }
  3944. }, FB.EventProvider)
  3945. );
  3946. /**
  3947. * @provides fb.dialog
  3948. * @requires fb.arbiter
  3949. * fb.array
  3950. * fb.content
  3951. * fb.dom
  3952. * fb.event
  3953. * fb.intl
  3954. * fb.obj
  3955. * fb.prelude
  3956. * fb.type
  3957. * fb.ua
  3958. * fb.xd
  3959. * @css fb.css.dialog
  3960. */
  3961. /**
  3962. * Copyright Facebook Inc.
  3963. *
  3964. * Licensed under the Apache License, Version 2.0 (the "License");
  3965. * you may not use this file except in compliance with the License.
  3966. * You may obtain a copy of the License at
  3967. *
  3968. * http://www.apache.org/licenses/LICENSE-2.0
  3969. *
  3970. * Unless required by applicable law or agreed to in writing, software
  3971. * distributed under the License is distributed on an "AS IS" BASIS,
  3972. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  3973. * See the License for the specific language governing permissions and
  3974. * limitations under the License.
  3975. */
  3976. /**
  3977. * Dialog creation and management.
  3978. * To get an object, do
  3979. * var dialog = FB.ui(...);
  3980. * To subscribe to an event, do
  3981. * FB.dialog.subscribe(
  3982. * '<event name>', function() { alert("<event name> happened"); });
  3983. * This dialog may fire the following events
  3984. * 'iframe_hide' This event is fired if an iframe dialog is hidden but not
  3985. * closed. Note that the dialog may subsequently reopen, for example if
  3986. * there was an error.
  3987. * 'iframe_show' This event is fired when an iframe dialog is first shown, or
  3988. * when an error dialog is shown.
  3989. * @class FB.Dialog
  3990. * @public
  3991. */
  3992. FB.subclass(
  3993. 'Dialog',
  3994. 'Obj',
  3995. /**
  3996. * constructor
  3997. * @param id
  3998. */
  3999. function(id) {
  4000. this.id = id;
  4001. if (!FB.Dialog._dialogs) {
  4002. FB.Dialog._dialogs = {};
  4003. FB.Dialog._addOrientationHandler();
  4004. }
  4005. FB.Dialog._dialogs[id] = this;
  4006. },
  4007. // Members
  4008. {
  4009. }
  4010. );
  4011. FB.provide('Dialog', {
  4012. /**
  4013. *
  4014. */
  4015. _dialogs: null,
  4016. _lastYOffset: 0,
  4017. /**
  4018. * The loader element.
  4019. *
  4020. * @access private
  4021. * @type DOMElement
  4022. */
  4023. _loaderEl: null,
  4024. /**
  4025. * A la Snowbox overlay underneath the dialog on iPad.
  4026. *
  4027. * @access private
  4028. * @type DOMElement
  4029. */
  4030. _overlayEl: null,
  4031. /**
  4032. * The stack of active dialogs.
  4033. *
  4034. * @access private
  4035. * @type Array
  4036. */
  4037. _stack: [],
  4038. /**
  4039. * The currently visible dialog.
  4040. *
  4041. * @access private
  4042. * @type DOMElement
  4043. */
  4044. _active: null,
  4045. /**
  4046. * The state of the popstate listener. Prevents multiple listeners from
  4047. * being created.
  4048. *
  4049. * @access private
  4050. * @type bool
  4051. */
  4052. _popStateListenerOn: false,
  4053. /**
  4054. * Hides open dialog on popstate event
  4055. *
  4056. * @access private
  4057. */
  4058. _hideOnPopState: function(e) {
  4059. FB.Dialog.hide(FB.Dialog._stack.pop());
  4060. },
  4061. /**
  4062. * Get dialog by id
  4063. * @access private
  4064. * @param id {string} dialog id
  4065. * @return {Dialog} a dialog object
  4066. */
  4067. get: function(id) {
  4068. return FB.Dialog._dialogs[id];
  4069. },
  4070. /**
  4071. * Find the root dialog node for a given element. This will walk up the DOM
  4072. * tree and while a node exists it will check to see if has the fb_dialog
  4073. * class and if it does returns it.
  4074. *
  4075. * @access private
  4076. * @param node {DOMElement} a child node of the dialog
  4077. * @return {DOMElement} the root dialog element if found
  4078. */
  4079. _findRoot: function(node) {
  4080. while (node) {
  4081. if (FB.Dom.containsCss(node, 'fb_dialog')) {
  4082. return node;
  4083. }
  4084. node = node.parentNode;
  4085. }
  4086. },
  4087. _createWWWLoader: function(width) {
  4088. width = parseInt(width, 10);
  4089. width = width ? width : 460;
  4090. return FB.Dialog.create({
  4091. content: (
  4092. '<div class="dialog_title">' +
  4093. ' <a id="fb_dialog_loader_close">' +
  4094. ' <div class="fb_dialog_close_icon"></div>' +
  4095. ' </a>' +
  4096. ' <span>Facebook</span>' +
  4097. ' <div style="clear:both;"></div>' +
  4098. '</div>' +
  4099. '<div class="dialog_content"></div>' +
  4100. '<div class="dialog_footer"></div>'),
  4101. width: width
  4102. });
  4103. },
  4104. _createMobileLoader: function() {
  4105. // This chrome is native when possible.
  4106. // We're copying the HTML/CSS output of an XHP element here
  4107. // pretty much verbatim to easily keep them in sync.
  4108. // Copied from e.g. facebook.com/dialog/feed as rendered
  4109. // for a mobile user agent.
  4110. var chrome = FB.UA.nativeApp() ?
  4111. '' :
  4112. ('<table>' +
  4113. ' <tbody>' +
  4114. ' <tr>' +
  4115. ' <td class="header_left">' +
  4116. ' <label class="touchable_button">' +
  4117. ' <input type="submit" value="' +
  4118. FB.Intl.tx._("Cancel") + '"' +
  4119. ' id="fb_dialog_loader_close"/>' +
  4120. ' </label>' +
  4121. ' </td>' +
  4122. ' <td class="header_center">' +
  4123. ' <div>' + FB.Intl.tx._("Loading...") + '</div>' +
  4124. ' </td>' +
  4125. ' <td class="header_right">' +
  4126. ' </td>' +
  4127. ' </tr>' +
  4128. ' </tbody>' +
  4129. '</table>');
  4130. return FB.Dialog.create({
  4131. classes: 'loading' + (FB.UA.iPad() ? ' centered' : ''),
  4132. content: (
  4133. '<div class="dialog_header">' +
  4134. chrome +
  4135. '</div>')
  4136. });
  4137. },
  4138. _restoreBodyPosition: function() {
  4139. if (!FB.UA.iPad()) {
  4140. var body = document.getElementsByTagName('body')[0];
  4141. FB.Dom.removeCss(body, 'fb_hidden');
  4142. }
  4143. },
  4144. _showIPadOverlay: function() {
  4145. if (!FB.UA.iPad()) {
  4146. return;
  4147. }
  4148. if (!FB.Dialog._overlayEl) {
  4149. FB.Dialog._overlayEl = document.createElement('div');
  4150. FB.Dialog._overlayEl.setAttribute('id', 'fb_dialog_ipad_overlay');
  4151. FB.Content.append(FB.Dialog._overlayEl, null);
  4152. }
  4153. FB.Dialog._overlayEl.className = '';
  4154. },
  4155. _hideIPadOverlay: function() {
  4156. if (FB.UA.iPad()) {
  4157. FB.Dialog._overlayEl.className = 'hidden';
  4158. }
  4159. },
  4160. /**
  4161. * Show the "Loading..." dialog. This is a special dialog which does not
  4162. * follow the standard stacking semantics. If a callback is provided, a
  4163. * cancel action is provided using the "X" icon.
  4164. *
  4165. * @param cb {Function} optional callback for the "X" action
  4166. */
  4167. showLoader: function(cb, width) {
  4168. FB.Dialog._showIPadOverlay();
  4169. if (!FB.Dialog._loaderEl) {
  4170. FB.Dialog._loaderEl = FB.Dialog._findRoot(
  4171. FB.UA.mobile()
  4172. ? FB.Dialog._createMobileLoader()
  4173. : FB.Dialog._createWWWLoader(width));
  4174. }
  4175. // this needs to be done for each invocation of showLoader. since we don't
  4176. // stack loaders and instead simply hold on to the last one, it is possible
  4177. // that we are showing nothing when we can potentially be showing the
  4178. // loading dialog for a previously activated but not yet loaded dialog.
  4179. if (!cb) {
  4180. cb = function() {};
  4181. }
  4182. var loaderClose = FB.$('fb_dialog_loader_close');
  4183. FB.Dom.removeCss(loaderClose, 'fb_hidden');
  4184. loaderClose.onclick = function() {
  4185. FB.Dialog._hideLoader();
  4186. FB.Dialog._restoreBodyPosition();
  4187. FB.Dialog._hideIPadOverlay();
  4188. cb();
  4189. };
  4190. var iPadOverlay = FB.$('fb_dialog_ipad_overlay');
  4191. if (iPadOverlay) {
  4192. iPadOverlay.ontouchstart = loaderClose.onclick;
  4193. }
  4194. FB.Dialog._makeActive(FB.Dialog._loaderEl);
  4195. },
  4196. /**
  4197. * Hide the loading dialog if one is being shown.
  4198. *
  4199. * @access private
  4200. */
  4201. _hideLoader: function() {
  4202. if (FB.Dialog._loaderEl && FB.Dialog._loaderEl == FB.Dialog._active) {
  4203. FB.Dialog._loaderEl.style.top = '-10000px';
  4204. }
  4205. },
  4206. /**
  4207. * Center a dialog based on its current dimensions and place it in the
  4208. * visible viewport area.
  4209. *
  4210. * @access private
  4211. * @param el {DOMElement} the dialog node
  4212. */
  4213. _makeActive: function(el) {
  4214. FB.Dialog._setDialogSizes();
  4215. FB.Dialog._lowerActive();
  4216. FB.Dialog._active = el;
  4217. if (FB.Canvas) {
  4218. FB.Canvas.getPageInfo(function(pageInfo) {
  4219. FB.Dialog._centerActive(pageInfo);
  4220. });
  4221. }
  4222. // use the cached version of the pageInfo if slow or failed arbiter
  4223. // or not in canvas
  4224. FB.Dialog._centerActive(FB.Canvas._pageInfo);
  4225. },
  4226. /**
  4227. * Lower the current active dialog if there is one.
  4228. *
  4229. * @access private
  4230. * @param node {DOMElement} the dialog node
  4231. */
  4232. _lowerActive: function() {
  4233. if (!FB.Dialog._active) {
  4234. return;
  4235. }
  4236. FB.Dialog._active.style.top = '-10000px';
  4237. FB.Dialog._active = null;
  4238. },
  4239. /**
  4240. * Remove the dialog from the stack.
  4241. *
  4242. * @access private
  4243. * @param node {DOMElement} the dialog node
  4244. */
  4245. _removeStacked: function(dialog) {
  4246. FB.Dialog._stack = FB.Array.filter(FB.Dialog._stack, function(node) {
  4247. return node != dialog;
  4248. });
  4249. },
  4250. /**
  4251. * Centers the active dialog vertically.
  4252. *
  4253. * @access private
  4254. */
  4255. _centerActive: function(pageInfo) {
  4256. var dialog = FB.Dialog._active;
  4257. if (!dialog) {
  4258. return;
  4259. }
  4260. var view = FB.Dom.getViewportInfo();
  4261. var width = parseInt(dialog.offsetWidth, 10);
  4262. var height = parseInt(dialog.offsetHeight, 10);
  4263. var left = view.scrollLeft + (view.width - width) / 2;
  4264. // Minimum and maximum values for the top of the dialog;
  4265. // these ensure that the dialog is always within the iframe's
  4266. // dimensions, with some padding.
  4267. // @todo(nikolay): When we refactor this module to avoid
  4268. // the excessive use of if (FB.UA.mobile()), get rid of
  4269. // this undesirable padding. It only looks bad on Desktop Safari
  4270. // (because of the scrollbars).
  4271. var minTop = (view.height - height) / 2.5;
  4272. if (left < minTop) {
  4273. minTop = left;
  4274. }
  4275. var maxTop = view.height - height - minTop;
  4276. // center vertically within the page
  4277. var top = (view.height - height) / 2;
  4278. if (pageInfo) {
  4279. top = pageInfo.scrollTop - pageInfo.offsetTop +
  4280. (pageInfo.clientHeight - height) / 2;
  4281. }
  4282. // clamp to min and max
  4283. if (top < minTop) {
  4284. top = minTop;
  4285. } else if (top > maxTop) {
  4286. top = maxTop;
  4287. }
  4288. // offset by the iframe's scroll
  4289. top += view.scrollTop;
  4290. // The body element is hidden at -10000px while we
  4291. // display dialogs. Full-screen on iPhone.
  4292. if (FB.UA.mobile()) {
  4293. // On mobile device (such as iPhone and iPad) that uses soft keyboard,
  4294. // when a text field has focus and the keyboard is shown, the OS will
  4295. // scroll a page to position the text field at the center of the remaining
  4296. // space. If page doesn't have enough height, then OS will effectively
  4297. // pull the page up by force while the keyboard is up, but the page will
  4298. // slide down as soon as the keyboard is hidden.
  4299. // When that happens, it can cause problems. For example, we had a nasty
  4300. // problem with typeahead control in app request dialog. When user types
  4301. // something in the control, the keyboard is up. However, when the user
  4302. // tap a selection, the keyboard disappears. If the page starts to scroll
  4303. // down, then the "click" event may fire from a differnt DOM element and
  4304. // cause wrong item (or no item) to be selected.
  4305. //
  4306. // After a lot of hacking around, the best solution we found is to insert
  4307. // an extra vertical padding element to give the page some extra space
  4308. // such that page won't be forced to scroll beyeond its limit when
  4309. // the text field inside the dialog needs to be centered. The negative
  4310. // side effect of this hack is that there will be some extra space
  4311. // that the user could scroll to.
  4312. var paddingHeight = 100;
  4313. // Smaller and centered on iPad. This should only run when the
  4314. // dialog is first rendered or the device rotated.
  4315. if (FB.UA.iPad()) {
  4316. paddingHeight += (view.height - height) / 2;
  4317. } else {
  4318. var body = document.getElementsByTagName('body')[0];
  4319. FB.Dom.addCss(body, 'fb_hidden');
  4320. left = 10000;
  4321. top = 10000;
  4322. }
  4323. var paddingDivs = FB.Dom.getByClass('fb_dialog_padding', dialog);
  4324. if (paddingDivs.length) {
  4325. paddingDivs[0].style.height = paddingHeight + 'px';
  4326. }
  4327. }
  4328. dialog.style.left = (left > 0 ? left : 0) + 'px';
  4329. dialog.style.top = (top > 0 ? top : 0) + 'px';
  4330. },
  4331. _setDialogSizes: function() {
  4332. if (!FB.UA.mobile() || FB.UA.iPad()) {
  4333. return;
  4334. }
  4335. for (var id in FB.Dialog._dialogs) {
  4336. if (document.getElementById(id)) {
  4337. var iframe = document.getElementById(id);
  4338. iframe.style.width = FB.UIServer.getDefaultSize().width + 'px';
  4339. iframe.style.height = FB.UIServer.getDefaultSize().height + 'px';
  4340. }
  4341. }
  4342. },
  4343. /**
  4344. * This adapt the position and orientation of the dialogs.
  4345. */
  4346. _handleOrientationChange: function(e) {
  4347. // Normally on Android, screen.availWidth/availHeight/width/height reflect
  4348. // values corresponding to the current orientation. In other words,
  4349. // width/height changes depending on orientation. However,
  4350. // on Android 2.3 browser, the values do not change at the time of the
  4351. // "orientation" event, but change shortly after (50-150ms later).
  4352. //
  4353. // This behavior is annoying. I now have to work around it by doing a
  4354. // timer pulling in the orientation event to detect the correct
  4355. // screen.availWidth/height now.
  4356. if (FB.UA.android() && screen.availWidth == FB.Dialog._availScreenWidth) {
  4357. window.setTimeout(FB.Dialog._handleOrientationChange, 50);
  4358. return;
  4359. }
  4360. FB.Dialog._availScreenWidth = screen.availWidth;
  4361. if (FB.UA.iPad()) {
  4362. FB.Dialog._centerActive();
  4363. } else {
  4364. for (var id in FB.Dialog._dialogs) {
  4365. // Resize the width of any iframes still on the page
  4366. if (document.getElementById(id)) {
  4367. document.getElementById(id).style.width =
  4368. FB.UIServer.getDefaultSize().width + 'px';
  4369. }
  4370. }
  4371. }
  4372. },
  4373. /**
  4374. * Add some logic to fire on orientation change.
  4375. */
  4376. _addOrientationHandler: function() {
  4377. if (!FB.UA.mobile()) {
  4378. return;
  4379. }
  4380. // onOrientationChange is fired on iOS and some Android devices,
  4381. // while other Android devices fire resize. Still other Android devices
  4382. // seem to fire neither.
  4383. var event_name = "onorientationchange" in window ?
  4384. 'orientationchange' :
  4385. 'resize';
  4386. FB.Dialog._availScreenWidth = screen.availWidth;
  4387. FB.Event.listen(window, event_name, FB.Dialog._handleOrientationChange);
  4388. },
  4389. /**
  4390. * Create a dialog. Returns the node of the dialog within which the caller
  4391. * can inject markup. Optional HTML string or a DOMElement can be passed in
  4392. * to be set as the content. Note, the dialog is hidden by default.
  4393. *
  4394. * @access protected
  4395. * @param opts {Object} Options:
  4396. * Property | Type | Description | Default
  4397. * --------- | ----------------- | --------------------------------- | -------
  4398. * content | String|DOMElement | HTML String or DOMElement |
  4399. * onClose | Boolean | callback if closed |
  4400. * closeIcon | Boolean | `true` to show close icon | `false`
  4401. * visible | Boolean | `true` to make visible | `false`
  4402. * width | Int | width of dialog | 'auto'
  4403. * classes | String | added to the dialog's classes | ''
  4404. *
  4405. * @return {DOMElement} the dialog content root
  4406. */
  4407. create: function(opts) {
  4408. opts = opts || {};
  4409. var
  4410. dialog = document.createElement('div'),
  4411. contentRoot = document.createElement('div'),
  4412. className = 'fb_dialog';
  4413. // optional close icon
  4414. if (opts.closeIcon && opts.onClose) {
  4415. var closeIcon = document.createElement('a');
  4416. closeIcon.className = 'fb_dialog_close_icon';
  4417. closeIcon.onclick = opts.onClose;
  4418. dialog.appendChild(closeIcon);
  4419. }
  4420. className += ' ' + (opts.classes || '');
  4421. // handle rounded corners j0nx
  4422. //#JSCOVERAGE_IF
  4423. if (FB.UA.ie()) {
  4424. className += ' fb_dialog_legacy';
  4425. FB.Array.forEach(
  4426. [
  4427. 'vert_left',
  4428. 'vert_right',
  4429. 'horiz_top',
  4430. 'horiz_bottom',
  4431. 'top_left',
  4432. 'top_right',
  4433. 'bottom_left',
  4434. 'bottom_right'
  4435. ],
  4436. function(name) {
  4437. var span = document.createElement('span');
  4438. span.className = 'fb_dialog_' + name;
  4439. dialog.appendChild(span);
  4440. }
  4441. );
  4442. } else {
  4443. className += (FB.UA.mobile())
  4444. ? ' fb_dialog_mobile'
  4445. : ' fb_dialog_advanced';
  4446. }
  4447. if (opts.content) {
  4448. FB.Content.append(opts.content, contentRoot);
  4449. }
  4450. dialog.className = className;
  4451. var width = parseInt(opts.width, 10);
  4452. if (!isNaN(width)) {
  4453. dialog.style.width = width + 'px';
  4454. }
  4455. contentRoot.className = 'fb_dialog_content';
  4456. dialog.appendChild(contentRoot);
  4457. if (FB.UA.mobile()) {
  4458. var padding = document.createElement('div');
  4459. padding.className = 'fb_dialog_padding';
  4460. dialog.appendChild(padding);
  4461. }
  4462. FB.Content.append(dialog);
  4463. if (opts.visible) {
  4464. FB.Dialog.show(dialog);
  4465. }
  4466. return contentRoot;
  4467. },
  4468. /**
  4469. * Raises the given iframe dialog. Any active dialogs are automatically
  4470. * lowered. An active loading indicator is suppressed. An already-lowered
  4471. * dialog will be raised and it will be put at the top of the stack. A dialog
  4472. * never shown before will be added to the top of the stack.
  4473. *
  4474. * @access protected
  4475. * @param dialog {DOMElement} a child element of the dialog
  4476. */
  4477. show: function(dialog) {
  4478. var root = FB.Dialog._findRoot(dialog);
  4479. if (root) {
  4480. FB.Dialog._removeStacked(root);
  4481. FB.Dialog._hideLoader();
  4482. FB.Dialog._makeActive(root);
  4483. FB.Dialog._stack.push(root);
  4484. if ('fbCallID' in dialog) {
  4485. FB.Dialog.get(dialog.fbCallID).fire('iframe_show');
  4486. }
  4487. if (!FB.Event._popStateListenerOn) {
  4488. FB.Event.listen(window, 'popstate', FB.Dialog._hideOnPopState);
  4489. FB.Event._popStateListenerOn = true;
  4490. }
  4491. }
  4492. },
  4493. /**
  4494. * Hide the given iframe dialog. The dialog will be lowered and moved out
  4495. * of view, but won't be removed.
  4496. *
  4497. * @access protected
  4498. * @param dialog {DOMElement} a child element of the dialog
  4499. */
  4500. hide: function(dialog) {
  4501. var root = FB.Dialog._findRoot(dialog);
  4502. if (root == FB.Dialog._active) {
  4503. FB.Dialog._lowerActive();
  4504. FB.Dialog._restoreBodyPosition();
  4505. FB.Dialog._hideIPadOverlay();
  4506. if ('fbCallID' in dialog) {
  4507. FB.Dialog.get(dialog.fbCallID).fire('iframe_hide');
  4508. }
  4509. if (FB.Event._popStateListenerOn) {
  4510. FB.Event.unlisten(window, 'popstate', FB.Dialog._hideOnPopState);
  4511. FB.Event._popStateListenerOn = false;
  4512. }
  4513. }
  4514. },
  4515. /**
  4516. * Remove the dialog, show any stacked dialogs.
  4517. *
  4518. * @access protected
  4519. * @param dialog {DOMElement} a child element of the dialog
  4520. */
  4521. remove: function(dialog) {
  4522. dialog = FB.Dialog._findRoot(dialog);
  4523. if (dialog) {
  4524. var is_active = FB.Dialog._active == dialog;
  4525. FB.Dialog._removeStacked(dialog);
  4526. if (is_active) {
  4527. FB.Dialog._hideLoader();
  4528. if (FB.Dialog._stack.length > 0) {
  4529. FB.Dialog.show(FB.Dialog._stack.pop());
  4530. } else {
  4531. FB.Dialog._lowerActive();
  4532. FB.Dialog._restoreBodyPosition();
  4533. FB.Dialog._hideIPadOverlay();
  4534. }
  4535. } else if (FB.Dialog._active === null && FB.Dialog._stack.length > 0) {
  4536. FB.Dialog.show(FB.Dialog._stack.pop());
  4537. }
  4538. // wait before the actual removal because of race conditions with async
  4539. // flash crap. seriously, dont ever ask me about it.
  4540. // if we remove this without deferring, then in IE only, we'll get an
  4541. // uncatchable error with no line numbers, function names, or stack
  4542. // traces. the 3 second delay isn't a problem because the <div> is
  4543. // already hidden, it's just not removed from the DOM yet.
  4544. window.setTimeout(function() {
  4545. dialog.parentNode.removeChild(dialog);
  4546. }, 3000);
  4547. }
  4548. },
  4549. /**
  4550. * Whether a given node is contained within the active dialog's root
  4551. *
  4552. * @access public
  4553. * @param dialog {DOMElement} a child element of the dialog
  4554. */
  4555. isActive: function(node) {
  4556. var root = FB.Dialog._findRoot(node);
  4557. return root && root === FB.Dialog._active;
  4558. }
  4559. });
  4560. /**
  4561. * Copyright Facebook Inc.
  4562. *
  4563. * Licensed under the Apache License, Version 2.0 (the "License");
  4564. * you may not use this file except in compliance with the License.
  4565. * You may obtain a copy of the License at
  4566. *
  4567. * http://www.apache.org/licenses/LICENSE-2.0
  4568. *
  4569. * Unless required by applicable law or agreed to in writing, software
  4570. * distributed under the License is distributed on an "AS IS" BASIS,
  4571. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  4572. * See the License for the specific language governing permissions and
  4573. * limitations under the License.
  4574. *
  4575. * @provides fb.ui
  4576. * @requires fb.prelude
  4577. * fb.canvas
  4578. * fb.content
  4579. * fb.dialog
  4580. * fb.qs
  4581. * fb.json
  4582. * fb.xd
  4583. * fb.arbiter
  4584. * fb.ua
  4585. */
  4586. /**
  4587. * UI Calls.
  4588. *
  4589. * @class FB
  4590. * @static
  4591. * @access private
  4592. */
  4593. FB.provide('', {
  4594. /**
  4595. * Method for triggering UI interaction with Facebook as iframe dialogs or
  4596. * popups, like publishing to the stream, sharing links.
  4597. *
  4598. * Example **stream.publish**:
  4599. *
  4600. * FB.ui(
  4601. * {
  4602. * method: 'stream.publish',
  4603. * message: 'getting educated about Facebook Connect',
  4604. * attachment: {
  4605. * name: 'Connect',
  4606. * caption: 'The Facebook Connect JavaScript SDK',
  4607. * description: (
  4608. * 'A small JavaScript library that allows you to harness ' +
  4609. * 'the power of Facebook, bringing the user\'s identity, ' +
  4610. * 'social graph and distribution power to your site.'
  4611. * ),
  4612. * href: 'http://github.com/facebook/connect-js'
  4613. * },
  4614. * action_links: [
  4615. * { text: 'Code', href: 'http://github.com/facebook/connect-js' }
  4616. * ],
  4617. * user_message_prompt: 'Share your thoughts about Connect'
  4618. * },
  4619. * function(response) {
  4620. * if (response && response.post_id) {
  4621. * alert('Post was published.');
  4622. * } else {
  4623. * alert('Post was not published.');
  4624. * }
  4625. * }
  4626. * );
  4627. *
  4628. * Example **stream.share**:
  4629. *
  4630. * var share = {
  4631. * method: 'stream.share',
  4632. * u: 'http://fbrell.com/'
  4633. * };
  4634. *
  4635. * FB.ui(share, function(response) { console.log(response); });
  4636. *
  4637. * @access public
  4638. * @param params {Object} The required arguments vary based on the method
  4639. * being used, but specifying the method itself is mandatory. If *display* is
  4640. * not specified, then iframe dialogs will be used when possible, and popups
  4641. * otherwise.
  4642. *
  4643. * Property | Type | Description | Argument
  4644. * -------- | ------- | ---------------------------------- | ------------
  4645. * method | String | The UI dialog to invoke. | **Required**
  4646. * display | String | Specify `"popup"` to force popups. | **Optional**
  4647. * @param cb {Function} Optional callback function to handle the result. Not
  4648. * all methods may have a response.
  4649. */
  4650. ui: function(params, cb) {
  4651. params = FB.copy({}, params);
  4652. if (!params.method) {
  4653. FB.log('"method" is a required parameter for FB.ui().');
  4654. return null;
  4655. }
  4656. // CORDOVA PATCH
  4657. // If the nativeInterface arg is specified then call out to the nativeInterface
  4658. // which uses the native app rather than using the iframe / popup web
  4659. if (FB._nativeInterface) {
  4660. switch (params.method) {
  4661. case 'auth.login':
  4662. FB._nativeInterface.login(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on login!' + e);});
  4663. break;
  4664. case 'permissions.request':
  4665. FB._nativeInterface.login(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on login!' + e);});
  4666. break;
  4667. case 'permissions.oauth':
  4668. FB._nativeInterface.login(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on login!' + e);});
  4669. break;
  4670. case 'auth.logout':
  4671. FB._nativeInterface.logout(cb, function(e) {alert('Cordova Facebook Connect plugin fail on logout!');});
  4672. break;
  4673. case 'auth.status':
  4674. FB._nativeInterface.getLoginStatus(cb, function(e) {alert('Cordova Facebook Connect plugin fail on auth.status!');});
  4675. break;
  4676. case 'login.status':
  4677. FB._nativeInterface.getLoginStatus(cb, function(e) {alert('Cordova Facebook Connect plugin fail on auth.status!');});
  4678. break;
  4679. case 'feed':
  4680. FB._nativeInterface.dialog(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on auth.status!');});
  4681. break;
  4682. case 'apprequests':
  4683. FB._nativeInterface.dialog(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on auth.status!');});
  4684. break;
  4685. }
  4686. return;
  4687. }
  4688. // process popup-only permissions
  4689. if ((params.method == 'permissions.request' ||
  4690. params.method == 'permissions.oauth') &&
  4691. (params.display == 'iframe' || params.display == 'dialog')) {
  4692. var perms;
  4693. var requested_perms;
  4694. perms = params.scope;
  4695. requested_perms = perms.split(/\s|,/g);
  4696. // OAuth2 spec says scope should be space delimited, but
  4697. // we previously accepted comma delimited strings. We'll accept both.
  4698. for (var i = 0; i < requested_perms.length; i++) {
  4699. var perm = FB.String.trim(requested_perms[i]);
  4700. // force a popup if we are not in the whitelist or we're set as
  4701. // false explicitly (and if the perm value is nonempty)
  4702. if (perm && !FB.initSitevars.iframePermissions[perm]) {
  4703. params.display = 'popup';
  4704. // we call this recursively to reprocess the prepareCall logic
  4705. // and make sure we'll pass the right parameters.
  4706. break;
  4707. }
  4708. }
  4709. }
  4710. var call = FB.UIServer.prepareCall(params, cb);
  4711. if (!call) { // aborted
  4712. return null;
  4713. }
  4714. // each allowed "display" value maps to a function
  4715. var displayName = call.params.display;
  4716. if (displayName === 'dialog') { // TODO remove once all dialogs are on
  4717. // uiserver
  4718. displayName = 'iframe';
  4719. } else if (displayName === 'none') {
  4720. displayName = 'hidden';
  4721. }
  4722. var displayFn = FB.UIServer[displayName];
  4723. if (!displayFn) {
  4724. FB.log('"display" must be one of "popup", ' +
  4725. '"dialog", "iframe", "touch", "async", "hidden", or "none"');
  4726. return null;
  4727. }
  4728. displayFn(call);
  4729. return call.dialog;
  4730. }
  4731. });
  4732. /**
  4733. * Internal UI functions.
  4734. *
  4735. * @class FB.UIServer
  4736. * @static
  4737. * @access private
  4738. */
  4739. FB.provide('UIServer', {
  4740. /**
  4741. * UI Methods will be defined in this namespace.
  4742. */
  4743. Methods: {},
  4744. // Child iframes or popup windows.
  4745. _loadedNodes : {},
  4746. _defaultCb : {},
  4747. _resultToken : '"xxRESULTTOKENxx"',
  4748. _forceHTTPS : false,
  4749. /**
  4750. * Serves as a generic transform for UI Server dialogs. Once all dialogs are
  4751. * built on UI Server, this will just become the default behavior.
  4752. *
  4753. * Current transforms:
  4754. * 1) display=dialog -> display=iframe. Most of the old Connect stuff uses
  4755. * dialog, but UI Server uses iframe.
  4756. * 2) Renaming of channel_url parameter to channel.
  4757. */
  4758. genericTransform: function(call) {
  4759. if (call.params.display == 'dialog' || call.params.display == 'iframe') {
  4760. call.params.display = 'iframe';
  4761. call.params.channel = FB.UIServer._xdChannelHandler(
  4762. call.id,
  4763. 'parent.parent'
  4764. );
  4765. }
  4766. return call;
  4767. },
  4768. /**
  4769. * Prepares a generic UI call. Some regular API call also go through
  4770. * here though via hidden iframes.
  4771. *
  4772. * @access private
  4773. * @param params {Object} the user supplied parameters
  4774. * @param cb {Function} the response callback
  4775. * @returns {Object} the call data
  4776. */
  4777. prepareCall: function(params, cb) {
  4778. var
  4779. name = params.method.toLowerCase(),
  4780. method = FB.copy({}, FB.UIServer.Methods[name]),
  4781. id = FB.guid(),
  4782. // TODO(naitik) don't want to force login status over HTTPS just yet. all
  4783. // other UI Server interactions will be forced over HTTPS,
  4784. // Methods can choose to not use https by setting noHttps=true
  4785. forceHTTPS = (method.noHttps !== true) &&
  4786. (FB._https ||
  4787. (name !== 'auth.status' && name != 'login.status'));
  4788. FB.UIServer._forceHTTPS = forceHTTPS;
  4789. // default stuff
  4790. FB.copy(params, {
  4791. api_key : FB._apiKey,
  4792. app_id : FB._apiKey,
  4793. locale : FB._locale,
  4794. sdk : 'joey',
  4795. access_token : forceHTTPS && FB.getAccessToken() || undefined
  4796. });
  4797. // overwrite display based on final param set
  4798. params.display = FB.UIServer.getDisplayMode(method, params);
  4799. // set the default dialog URL if one doesn't exist
  4800. if (!method.url) {
  4801. method.url = 'dialog/' + name;
  4802. }
  4803. // the basic call data
  4804. var call = {
  4805. cb : cb,
  4806. id : id,
  4807. size : method.size || FB.UIServer.getDefaultSize(),
  4808. url : FB.getDomain(forceHTTPS ? 'https_www' : 'www') + method.url,
  4809. forceHTTPS: forceHTTPS,
  4810. params : params,
  4811. name : name,
  4812. dialog : new FB.Dialog(id)
  4813. };
  4814. // optional method transform
  4815. var transform = method.transform
  4816. ? method.transform
  4817. : FB.UIServer.genericTransform;
  4818. if (transform) {
  4819. call = transform(call);
  4820. // nothing returned from a transform means we abort
  4821. if (!call) {
  4822. return;
  4823. }
  4824. }
  4825. // setting these after to ensure the value is based on the final
  4826. // params.display value
  4827. var getXdRelationFn = method.getXdRelation || FB.UIServer.getXdRelation;
  4828. var relation = getXdRelationFn(call.params);
  4829. if (!(call.id in FB.UIServer._defaultCb) &&
  4830. !('next' in call.params) &&
  4831. !('redirect_uri' in call.params)) {
  4832. call.params.next = FB.UIServer._xdResult(
  4833. call.cb,
  4834. call.id,
  4835. relation,
  4836. true // isDefault
  4837. );
  4838. }
  4839. if (relation === 'parent') {
  4840. call.params.channel_url = FB.UIServer._xdChannelHandler(
  4841. id,
  4842. 'parent.parent'
  4843. );
  4844. }
  4845. // Encode the params as a query string or in the fragment
  4846. call = FB.UIServer.prepareParams(call);
  4847. return call;
  4848. },
  4849. prepareParams: function(call) {
  4850. var method = call.params.method;
  4851. // Page iframes still hit /fbml/ajax/uiserver.php
  4852. // which uses the old method names.
  4853. // On the other hand, the new endpoint might not expect
  4854. // the method as a param.
  4855. if (!FB.Canvas.isTabIframe()) {
  4856. delete call.params.method;
  4857. }
  4858. if (FB.TemplateUI && FB.TemplateUI.supportsTemplate(method, call)) {
  4859. // Temporary debug info.
  4860. if (FB.reportTemplates) {
  4861. console.log("Using template for " + method + ".");
  4862. }
  4863. FB.TemplateUI.useCachedUI(method, call);
  4864. } else {
  4865. // flatten parameters as needed
  4866. call.params = FB.JSON.flatten(call.params);
  4867. var encodedQS = FB.QS.encode(call.params);
  4868. // To overcome the QS length limitation on some browsers
  4869. // (the fb native app is an exception because it doesn't
  4870. // doesn't support POST for dialogs).
  4871. if (!FB.UA.nativeApp() &&
  4872. FB.UIServer.urlTooLongForIE(call.url + '?' + encodedQS)) {
  4873. call.post = true;
  4874. } else if (encodedQS) {
  4875. call.url += '?' + encodedQS;
  4876. }
  4877. }
  4878. return call;
  4879. },
  4880. urlTooLongForIE: function(fullURL) {
  4881. return fullURL.length > 2000;
  4882. },
  4883. /**
  4884. * Determine the display mode for the call.
  4885. *
  4886. * @param method {Object} the method definition object
  4887. * @param params {Object} the developer supplied parameters
  4888. * @return {String} the display mode
  4889. */
  4890. getDisplayMode: function(method, params) {
  4891. if (params.display === 'hidden' ||
  4892. params.display === 'none') {
  4893. return params.display;
  4894. }
  4895. if (FB.Canvas.isTabIframe() &&
  4896. params.display !== 'popup') {
  4897. return 'async';
  4898. }
  4899. // For mobile, we should use touch display mode
  4900. if (FB.UA.mobile() || params.display === 'touch') {
  4901. return 'touch';
  4902. }
  4903. // cannot use an iframe "dialog" if an access token is not available
  4904. if (!FB.getAccessToken() &&
  4905. params.display == 'dialog' &&
  4906. !method.loggedOutIframe) {
  4907. FB.log('"dialog" mode can only be used when the user is connected.');
  4908. return 'popup';
  4909. }
  4910. if (method.connectDisplay && !FB._inCanvas) {
  4911. return method.connectDisplay;
  4912. }
  4913. // TODO change "dialog" to "iframe" once moved to uiserver
  4914. return params.display || (FB.getAccessToken() ? 'dialog' : 'popup');
  4915. },
  4916. /**
  4917. * Determine the frame relation for given params
  4918. *
  4919. * @param params {Object} the call params
  4920. * @return {String} the relation string
  4921. */
  4922. getXdRelation: function(params) {
  4923. var display = params.display;
  4924. if (display === 'popup' || display === 'touch') {
  4925. return 'opener';
  4926. }
  4927. if (display === 'dialog' || display === 'iframe' ||
  4928. display === 'hidden' || display === 'none') {
  4929. return 'parent';
  4930. }
  4931. if (display === 'async') {
  4932. return 'parent.frames[' + window.name + ']';
  4933. }
  4934. },
  4935. /**
  4936. * Open a popup window with the given url and dimensions and place it at the
  4937. * center of the current window.
  4938. *
  4939. * @access private
  4940. * @param call {Object} the call data
  4941. */
  4942. popup: function(call) {
  4943. // we try to place it at the center of the current window
  4944. var
  4945. _screenX = typeof window.screenX != 'undefined'
  4946. ? window.screenX
  4947. : window.screenLeft,
  4948. screenY = typeof window.screenY != 'undefined'
  4949. ? window.screenY
  4950. : window.screenTop,
  4951. outerWidth = typeof window.outerWidth != 'undefined'
  4952. ? window.outerWidth
  4953. : document.documentElement.clientWidth,
  4954. outerHeight = typeof window.outerHeight != 'undefined'
  4955. ? window.outerHeight
  4956. : (document.documentElement.clientHeight - 22), // 22= IE toolbar height
  4957. // Mobile popups should never specify width/height features since it
  4958. // messes with the dimension styles of the page layout.
  4959. width = FB.UA.mobile() ? null : call.size.width,
  4960. height = FB.UA.mobile() ? null : call.size.height,
  4961. screenX = (_screenX < 0) ? window.screen.width + _screenX : _screenX,
  4962. left = parseInt(screenX + ((outerWidth - width) / 2), 10),
  4963. top = parseInt(screenY + ((outerHeight - height) / 2.5), 10),
  4964. features = [];
  4965. if (width !== null) {
  4966. features.push('width=' + width);
  4967. }
  4968. if (height !== null) {
  4969. features.push('height=' + height);
  4970. }
  4971. features.push('left=' + left);
  4972. features.push('top=' + top);
  4973. features.push('scrollbars=1');
  4974. if (call.name == 'permissions.request' ||
  4975. call.name == 'permissions.oauth') {
  4976. features.push('location=1,toolbar=0');
  4977. }
  4978. features = features.join(',');
  4979. // either a empty window and then a POST, or a direct GET to the full url
  4980. if (call.post) {
  4981. FB.UIServer.setLoadedNode(call,
  4982. window.open('about:blank', call.id, features), 'popup');
  4983. FB.Content.submitToTarget({
  4984. url : call.url,
  4985. target : call.id,
  4986. params : call.params
  4987. });
  4988. } else {
  4989. FB.UIServer.setLoadedNode(call,
  4990. window.open(call.url, call.id, features), 'popup');
  4991. }
  4992. // if there's a default close action, setup the monitor for it
  4993. if (call.id in FB.UIServer._defaultCb) {
  4994. FB.UIServer._popupMonitor();
  4995. }
  4996. },
  4997. setLoadedNode: function(call, node, type) {
  4998. if (call.params && call.params.display != 'popup') {
  4999. // Note that we avoid setting fbCallID property on node when
  5000. // display is popup because when the page is loaded via http,
  5001. // you can't set a property on an https popup window in IE.
  5002. node.fbCallID = call.id;
  5003. }
  5004. node = {
  5005. node: node,
  5006. type: type,
  5007. fbCallID: call.id
  5008. };
  5009. FB.UIServer._loadedNodes[call.id] = node;
  5010. },
  5011. getLoadedNode: function(call) {
  5012. var id = typeof call == 'object' ? call.id : call,
  5013. node = FB.UIServer._loadedNodes[id];
  5014. return node ? node.node : null;
  5015. },
  5016. /**
  5017. * Builds and inserts a hidden iframe based on the given call data.
  5018. *
  5019. * @access private
  5020. * @param call {Object} the call data
  5021. */
  5022. hidden: function(call) {
  5023. call.className = 'FB_UI_Hidden';
  5024. call.root = FB.Content.appendHidden('');
  5025. FB.UIServer._insertIframe(call);
  5026. },
  5027. /**
  5028. * Builds and inserts a iframe dialog based on the given call data.
  5029. *
  5030. * @access private
  5031. * @param call {Object} the call data
  5032. */
  5033. iframe: function(call) {
  5034. call.className = 'FB_UI_Dialog';
  5035. var onClose = function() {
  5036. FB.UIServer._triggerDefault(call.id);
  5037. };
  5038. call.root = FB.Dialog.create({
  5039. onClose: onClose,
  5040. closeIcon: true,
  5041. classes: (FB.UA.iPad() ? 'centered' : '')
  5042. });
  5043. if (!call.hideLoader) {
  5044. FB.Dialog.showLoader(onClose, call.size.width);
  5045. }
  5046. FB.Dom.addCss(call.root, 'fb_dialog_iframe');
  5047. FB.UIServer._insertIframe(call);
  5048. },
  5049. /**
  5050. * Display an overlay dialog on a mobile device. This works both in the native
  5051. * mobile canvas frame as well as a regular mobile web browser.
  5052. *
  5053. * @access private
  5054. * @param call {Object} the call data
  5055. */
  5056. touch: function(call) {
  5057. if (call.params && call.params.in_iframe) {
  5058. // Cached dialog was already created. Still show loader while it runs
  5059. // JS to adapt its content to the FB.ui params.
  5060. if (call.ui_created) {
  5061. FB.Dialog.showLoader(function() {
  5062. FB.UIServer._triggerDefault(call.id);
  5063. }, 0);
  5064. } else {
  5065. FB.UIServer.iframe(call);
  5066. }
  5067. } else if (FB.UA.nativeApp() && !call.ui_created) {
  5068. // When running inside native app, window.open is not supported.
  5069. // We need to create an webview using custom JS bridge function
  5070. call.frame = call.id;
  5071. FB.Native.onready(function() {
  5072. // TODO:
  5073. // We normally use window.name to pass cb token, but
  5074. // FB.Native.open doesn't accept a name parameter that it
  5075. // can pass to webview, so we use pass name through
  5076. // fragment for now. We should investigate to see if we can
  5077. // pass a window.name
  5078. FB.UIServer.setLoadedNode(call, FB.Native.open(
  5079. call.url + '#cb=' + call.frameName));
  5080. });
  5081. FB.UIServer._popupMonitor();
  5082. } else if (!call.ui_created) {
  5083. // Use popup by default
  5084. FB.UIServer.popup(call);
  5085. }
  5086. },
  5087. /**
  5088. * This is used when the application is running as a child iframe on
  5089. * facebook.com. This flow involves sending a message to the parent frame and
  5090. * asking it to render the UIServer dialog as part of the Facebook chrome.
  5091. *
  5092. * @access private
  5093. * @param call {Object} the call data
  5094. */
  5095. async: function(call) {
  5096. call.frame = window.name;
  5097. delete call.url;
  5098. delete call.size;
  5099. FB.Arbiter.inform('showDialog', call);
  5100. },
  5101. getDefaultSize: function() {
  5102. if (FB.UA.mobile()) {
  5103. if (FB.UA.iPad()) {
  5104. return {
  5105. width: 500,
  5106. height: 590
  5107. };
  5108. } else if (FB.UA.android()) {
  5109. // Android browser needs special handling because
  5110. // window.innerWidth/Height doesn't return correct values
  5111. return {
  5112. width: screen.availWidth,
  5113. height: screen.availHeight
  5114. };
  5115. } else {
  5116. var width = window.innerWidth;
  5117. var height = window.innerHeight;
  5118. var isLandscape = width / height > 1.2;
  5119. // Make sure that the iframe width is not greater than screen width.
  5120. // We also start by calculating full screen height. In that case,
  5121. // window.innerHeight is not good enough because it doesn't take into
  5122. // account the height of address bar, etc. So we tried to use
  5123. // screen.width/height, but that alone is also not good enough because
  5124. // screen value is physical pixel value, but we need virtual pixel
  5125. // value because the virtual pixels value can be different from physical
  5126. // values depending on viewport meta tags.
  5127. // So in the end, we use the maximum value. It is OK
  5128. // if the height is too high because our new mobile dialog flow the
  5129. // content from top down.
  5130. return {
  5131. width: width,
  5132. height: Math.max(height,
  5133. (isLandscape ? screen.width : screen.height))
  5134. };
  5135. }
  5136. }
  5137. return {width: 575, height: 240};
  5138. },
  5139. /**
  5140. * Inserts an iframe based on the given call data.
  5141. *
  5142. * @access private
  5143. * @param call {Object} the call data
  5144. */
  5145. _insertIframe: function(call) {
  5146. // the dialog may be cancelled even before we have a valid iframe node
  5147. // giving us a race condition. if this happens, the call.id will be removed
  5148. // from the _frames nodes, and we won't add the node back in.
  5149. FB.UIServer._loadedNodes[call.id] = false;
  5150. var activate = function(node) {
  5151. if (call.id in FB.UIServer._loadedNodes) {
  5152. FB.UIServer.setLoadedNode(call, node, 'iframe');
  5153. }
  5154. };
  5155. // either a empty iframe and then a POST, or a direct GET to the full url
  5156. if (call.post) {
  5157. FB.Content.insertIframe({
  5158. url : 'about:blank',
  5159. root : call.root,
  5160. className : call.className,
  5161. width : call.size.width,
  5162. height : call.size.height,
  5163. id : call.id,
  5164. onInsert : activate,
  5165. onload : function(node) {
  5166. FB.Content.submitToTarget({
  5167. url : call.url,
  5168. target : node.name,
  5169. params : call.params
  5170. });
  5171. }
  5172. });
  5173. } else {
  5174. FB.Content.insertIframe({
  5175. url : call.url,
  5176. root : call.root,
  5177. className : call.className,
  5178. width : call.size.width,
  5179. height : call.size.height,
  5180. id : call.id,
  5181. name : call.frameName,
  5182. onInsert : activate
  5183. });
  5184. }
  5185. },
  5186. /**
  5187. * @param frame {String} the id of the iframe being resized
  5188. * @param data {Object} data from the XD call it made
  5189. *
  5190. * @access private
  5191. */
  5192. _handleResizeMessage: function(frame, data) {
  5193. var node = FB.UIServer.getLoadedNode(frame);
  5194. if (!node) {
  5195. return;
  5196. }
  5197. if (data.height) {
  5198. node.style.height = data.height + 'px';
  5199. }
  5200. if (data.width) {
  5201. node.style.width = data.width + 'px';
  5202. }
  5203. FB.Arbiter.inform(
  5204. 'resize.ack',
  5205. data || {},
  5206. 'parent.frames[' + node.name + ']',
  5207. true);
  5208. if (!FB.Dialog.isActive(node)) {
  5209. FB.Dialog.show(node);
  5210. }
  5211. },
  5212. /**
  5213. * Trigger the default action for the given call id.
  5214. *
  5215. * @param id {String} the call id
  5216. */
  5217. _triggerDefault: function(id) {
  5218. FB.UIServer._xdRecv(
  5219. { frame: id },
  5220. FB.UIServer._defaultCb[id] || function() {}
  5221. );
  5222. },
  5223. /**
  5224. * Start and manage the window monitor interval. This allows us to invoke
  5225. * the default callback for a window when the user closes the window
  5226. * directly.
  5227. *
  5228. * @access private
  5229. */
  5230. _popupMonitor: function() {
  5231. // check all open windows
  5232. var found;
  5233. for (var id in FB.UIServer._loadedNodes) {
  5234. // ignore prototype properties, and ones without a default callback
  5235. if (FB.UIServer._loadedNodes.hasOwnProperty(id) &&
  5236. id in FB.UIServer._defaultCb) {
  5237. var node = FB.UIServer._loadedNodes[id];
  5238. if (node.type != 'popup') {
  5239. continue;
  5240. }
  5241. win = node.node;
  5242. try {
  5243. // found a closed window
  5244. if (win.closed) {
  5245. FB.UIServer._triggerDefault(id);
  5246. } else {
  5247. found = true; // need to monitor this open window
  5248. }
  5249. } catch (y) {
  5250. // probably a permission error
  5251. }
  5252. }
  5253. }
  5254. if (found && !FB.UIServer._popupInterval) {
  5255. // start the monitor if needed and it's not already running
  5256. FB.UIServer._popupInterval = window.setInterval(
  5257. FB.UIServer._popupMonitor,
  5258. 100
  5259. );
  5260. } else if (!found && FB.UIServer._popupInterval) {
  5261. // shutdown if we have nothing to monitor but it's running
  5262. window.clearInterval(FB.UIServer._popupInterval);
  5263. FB.UIServer._popupInterval = null;
  5264. }
  5265. },
  5266. /**
  5267. * Handles channel messages that do not kill the dialog or remove the handler.
  5268. * Terminating logic should be handled within the "next" handler.
  5269. *
  5270. * @access private
  5271. * @param frame {String} the frame id
  5272. * @param relation {String} the frame relation
  5273. * @return {String} the handler url
  5274. */
  5275. _xdChannelHandler: function(frame, relation) {
  5276. var forceHTTPS = (FB.UIServer._forceHTTPS &&
  5277. FB.UA.ie() !== 7);
  5278. return FB.XD.handler(function(data) {
  5279. var node = FB.UIServer.getLoadedNode(frame);
  5280. if (!node) { // dead handler
  5281. return;
  5282. }
  5283. if (data.type == 'resize') {
  5284. FB.UIServer._handleResizeMessage(frame, data);
  5285. } else if (data.type == 'hide') {
  5286. FB.Dialog.hide(node);
  5287. } else if (data.type == 'rendered') {
  5288. var root = FB.Dialog._findRoot(node);
  5289. FB.Dialog.show(root);
  5290. } else if (data.type == 'fireevent') {
  5291. FB.Event.fire(data.event);
  5292. }
  5293. }, relation, true, null, forceHTTPS);
  5294. },
  5295. /**
  5296. * A "next handler" is a specialized XD handler that will also close the
  5297. * frame. This can be a hidden iframe, iframe dialog or a popup window.
  5298. * Once it is fired it is also deleted.
  5299. *
  5300. * @access private
  5301. * @param cb {Function} the callback function
  5302. * @param frame {String} frame id for the callback will be used with
  5303. * @param relation {String} parent or opener to indicate window relation
  5304. * @param isDefault {Boolean} is this the default callback for the frame
  5305. * @return {String} the xd url bound to the callback
  5306. */
  5307. _xdNextHandler: function(cb, frame, relation, isDefault) {
  5308. if (isDefault) {
  5309. FB.UIServer._defaultCb[frame] = cb;
  5310. }
  5311. return FB.XD.handler(function(data) {
  5312. FB.UIServer._xdRecv(data, cb);
  5313. }, relation) + '&frame=' + frame;
  5314. },
  5315. /**
  5316. * Handles the parsed message, invokes the bound callback with the data and
  5317. * removes the related window/frame. This is the asynchronous entry point for
  5318. * when a message arrives.
  5319. *
  5320. * @access private
  5321. * @param data {Object} the message parameters
  5322. * @param cb {Function} the callback function
  5323. */
  5324. _xdRecv: function(data, cb) {
  5325. var frame = FB.UIServer.getLoadedNode(data.frame);
  5326. if (frame) {
  5327. // iframe
  5328. try {
  5329. if (FB.Dom.containsCss(frame, 'FB_UI_Hidden')) {
  5330. // wait before the actual removal because of race conditions with
  5331. // async flash crap. seriously, dont ever ask me about it.
  5332. window.setTimeout(function() {
  5333. // remove the parentNode to match what FB.UIServer.hidden() does
  5334. frame.parentNode.parentNode.removeChild(frame.parentNode);
  5335. }, 3000);
  5336. } else if (FB.Dom.containsCss(frame, 'FB_UI_Dialog')) {
  5337. FB.Dialog.remove(frame);
  5338. if (FB.TemplateUI && FB.UA.mobile()) {
  5339. FB.TemplateUI.populateCache();
  5340. }
  5341. }
  5342. } catch (x) {
  5343. // do nothing, permission error
  5344. }
  5345. // popup window
  5346. try {
  5347. if (frame.close) {
  5348. frame.close();
  5349. FB.UIServer._popupCount--;
  5350. }
  5351. } catch (y) {
  5352. // do nothing, permission error
  5353. }
  5354. }
  5355. // cleanup and fire
  5356. delete FB.UIServer._loadedNodes[data.frame];
  5357. delete FB.UIServer._defaultCb[data.frame];
  5358. cb(data);
  5359. },
  5360. /**
  5361. * Some Facebook redirect URLs use a special ``xxRESULTTOKENxx`` to return
  5362. * custom values. This is a convenience function to wrap a callback that
  5363. * expects this value back.
  5364. *
  5365. * @access private
  5366. * @param cb {Function} the callback function
  5367. * @param frame {String} the frame id for the callback is tied to
  5368. * @param target {String} parent or opener to indicate window relation
  5369. * @param isDefault {Boolean} is this the default callback for the frame
  5370. * @return {String} the xd url bound to the callback
  5371. */
  5372. _xdResult: function(cb, frame, target, isDefault) {
  5373. return (
  5374. FB.UIServer._xdNextHandler(function(params) {
  5375. cb && cb(params.result &&
  5376. params.result != FB.UIServer._resultToken &&
  5377. FB.JSON.parse(params.result));
  5378. }, frame, target, isDefault) +
  5379. '&result=' + encodeURIComponent(FB.UIServer._resultToken)
  5380. );
  5381. }
  5382. });
  5383. /**
  5384. * Copyright Facebook Inc.
  5385. *
  5386. * Licensed under the Apache License, Version 2.0 (the "License");
  5387. * you may not use this file except in compliance with the License.
  5388. * You may obtain a copy of the License at
  5389. *
  5390. * http://www.apache.org/licenses/LICENSE-2.0
  5391. *
  5392. * Unless required by applicable law or agreed to in writing, software
  5393. * distributed under the License is distributed on an "AS IS" BASIS,
  5394. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  5395. * See the License for the specific language governing permissions and
  5396. * limitations under the License.
  5397. *
  5398. *
  5399. *
  5400. * @provides fb.auth
  5401. * @requires fb.prelude
  5402. * fb.qs
  5403. * fb.event
  5404. * fb.json
  5405. * fb.ui
  5406. * fb.ua
  5407. */
  5408. /**
  5409. * Authentication and Authorization.
  5410. *
  5411. * @class FB
  5412. * @static
  5413. * @access private
  5414. */
  5415. FB.provide('', {
  5416. /**
  5417. * Find out the current status from the server, and get an authResponse if
  5418. * the user is connected.
  5419. *
  5420. * The user's status or the question of *who is the current user* is
  5421. * the first thing you will typically start with. For the answer, we
  5422. * ask facebook.com. Facebook will answer this question in one of
  5423. * two ways:
  5424. *
  5425. * 1. Someone you don't know.
  5426. * 2. Someone you know and have interacted with.
  5427. * Here's an authResponse for them.
  5428. *
  5429. * FB.getLoginStatus(function(response) {
  5430. * if (response.authResponse) {
  5431. * FB.assert(response.status === 'connected');
  5432. * // logged in and connected user, someone you know
  5433. * } else if (response.status === 'not_authorized') {
  5434. * // the user is logged in but not connected to the application
  5435. * } else {
  5436. * FB.assert(response.status === 'unknown');
  5437. * // the user isn't even logged in to Facebook.
  5438. * }
  5439. * });
  5440. *
  5441. * **Events**
  5442. *
  5443. * #### auth.login
  5444. * This event is fired when your application first notices the user (in other
  5445. * words, gets an authResponse when it didn't already have a valid one).
  5446. * #### auth.logout
  5447. * This event is fired when your application notices that there is no longer
  5448. * a valid user (in other words, it had an authResponse but can no longer
  5449. * validate the current user).
  5450. * #### auth.authResponseChange
  5451. * This event is fired for **any** auth related change as they all affect the
  5452. * access token: login, logout, and access token refresh. Access tokens are
  5453. * are refreshed over time as long as the user is active with your
  5454. * application.
  5455. * #### auth.statusChange
  5456. * Typically you will want to use the auth.authResponseChange event,
  5457. * but in rare cases, you want to distinguish between these three states:
  5458. *
  5459. * - Connected
  5460. * - Logged into Facebook but not connected with your application
  5461. * - Not logged into Facebook at all.
  5462. *
  5463. * The [FB.Event.subscribe][subscribe] and
  5464. * [FB.Event.unsubscribe][unsubscribe] functions are used to subscribe to
  5465. * these events. For example:
  5466. *
  5467. * FB.Event.subscribe('auth.login', function(response) {
  5468. * // do something with response
  5469. * });
  5470. *
  5471. * The response object returned to all these events is the same as the
  5472. * response from [FB.getLoginStatus][getLoginStatus], [FB.login][login] or
  5473. * [FB.logout][logout]. This response object contains:
  5474. *
  5475. * status
  5476. * : The status of the User. One of `connected`, `notConnected` or `unknown`.
  5477. *
  5478. * authResponse
  5479. * : The authorization response. The field is presented if and only if the
  5480. * user is logged in and connected to your app.
  5481. *
  5482. * [subscribe]: /docs/reference/javascript/FB.Event.subscribe
  5483. * [unsubscribe]: /docs/reference/javascript/FB.Event.unsubscribe
  5484. * [getLoginStatus]: /docs/reference/javascript/FB.getLoginStatus
  5485. * [login]: /docs/reference/javascript/FB.login
  5486. * [logout]: /docs/reference/javascript/FB.logout
  5487. *
  5488. * @access public
  5489. * @param cb {Function} The callback function.
  5490. * @param force {Boolean} Force reloading the login status (default `false`).
  5491. */
  5492. getLoginStatus: function(cb, force) {
  5493. if (!FB._apiKey) {
  5494. FB.log('FB.getLoginStatus() called before calling FB.init().');
  5495. return;
  5496. }
  5497. // we either invoke the callback right away if the status has already been
  5498. // loaded, or queue it up for when the load is done.
  5499. if (cb) {
  5500. if (!force && FB.Auth._loadState == 'loaded') {
  5501. cb({ status: FB._userStatus,
  5502. authResponse: FB._authResponse});
  5503. return;
  5504. } else {
  5505. FB.Event.subscribe('FB.loginStatus', cb);
  5506. }
  5507. }
  5508. // if we're already loading, and this is not a force load, we're done
  5509. if (!force && FB.Auth._loadState == 'loading') {
  5510. return;
  5511. }
  5512. FB.Auth._loadState = 'loading';
  5513. // invoke the queued callbacks
  5514. var lsCb = function(response) {
  5515. // done
  5516. FB.Auth._loadState = 'loaded';
  5517. // invoke callbacks
  5518. FB.Event.fire('FB.loginStatus', response);
  5519. FB.Event.clear('FB.loginStatus');
  5520. };
  5521. FB.Auth.fetchLoginStatus(lsCb);
  5522. },
  5523. /**
  5524. * Returns the full packet of information about the user and
  5525. * his or her access token, or null if there's no active access
  5526. * token. This packet is referred to as the authorization response.
  5527. *
  5528. * @access public
  5529. * return {Object} a record containing the access token, then user id,
  5530. * the amount of time before it expires, and the
  5531. * signed request (or null if there's no active access token).
  5532. */
  5533. getAuthResponse: function() {
  5534. return FB._authResponse;
  5535. },
  5536. /**
  5537. * Returns the access token embedded within the authResponse
  5538. * (or null if it's not available).
  5539. *
  5540. * @access public
  5541. * @return {String} the access token, if available, or null if not.
  5542. */
  5543. getAccessToken: function() {
  5544. return (FB._authResponse && FB._authResponse.accessToken) || null;
  5545. },
  5546. /**
  5547. * Returns the ID of the connected user, or 0 if
  5548. * the user is logged out or otherwise couldn't be
  5549. * discerned from cookie or access token information.
  5550. *
  5551. * @access public
  5552. * @return {Integer} the ID of the logged in, connected user.
  5553. */
  5554. getUserID: function() {
  5555. return FB._userID;
  5556. },
  5557. /**
  5558. * Login/Authorize/Permissions.
  5559. *
  5560. * Once you have determined the user's status, you may need to
  5561. * prompt the user to login. It is best to delay this action to
  5562. * reduce user friction when they first arrive at your site. You can
  5563. * then prompt and show them the "Connect with Facebook" button
  5564. * bound to an event handler which does the following:
  5565. *
  5566. * FB.login(function(response) {
  5567. * if (response.authResponse) {
  5568. * // user successfully logged in
  5569. * } else {
  5570. * // user cancelled login
  5571. * }
  5572. * });
  5573. *
  5574. * You should **only** call this on a user event as it opens a
  5575. * popup. Most browsers block popups, _unless_ they were initiated
  5576. * from a user event, such as a click on a button or a link.
  5577. *
  5578. *
  5579. * Depending on your application's needs, you may need additional
  5580. * permissions from the user. A large number of calls do not require
  5581. * any additional permissions, so you should first make sure you
  5582. * need a permission. This is a good idea because this step
  5583. * potentially adds friction to the user's process. Another point to
  5584. * remember is that this call can be made even _after_ the user has
  5585. * first connected. So you may want to delay asking for permissions
  5586. * until as late as possible:
  5587. *
  5588. * FB.login(function(response) {
  5589. * if (response.authResponse) {
  5590. * // if you need to know which permissions were granted then
  5591. * // you can can make an fql-call
  5592. * FB.api({
  5593. * method: 'fql.query',
  5594. * query: 'select read_stream, publish_stream, ' +
  5595. * 'offline_access from permissions where uid=me()'
  5596. * },
  5597. * function (data) {
  5598. * if (data[0].read_stream) {
  5599. * // we have read_stream
  5600. * }
  5601. * });
  5602. * } else {
  5603. * // user is not logged in
  5604. * }
  5605. * }, {scope:'read_stream, publish_stream, offline_access'});
  5606. *
  5607. * @access public
  5608. * @param cb {Function} The callback function.
  5609. * @param opts {Object} (_optional_) Options to modify login behavior.
  5610. *
  5611. * Name | Type | Description
  5612. * ------------------------ | ------- | -------------------------------------
  5613. * enable_profile_selector | Boolean | When true, prompt the user to grant
  5614. * | | permission for one or more Pages.
  5615. * profile_selector_ids | String | Comma separated list of IDs to
  5616. * | | display in the profile selector.
  5617. * scope | String | Comma or space delimited list of
  5618. * | | [Extended permissions]
  5619. * | | (/docs/authentication/permissions).
  5620. */
  5621. login: function(cb, opts) {
  5622. if (opts && opts.perms && !opts.scope) {
  5623. opts.scope = opts.perms;
  5624. delete opts.perms;
  5625. FB.log('OAuth2 specification states that \'perms\' ' +
  5626. 'should now be called \'scope\'. Please update.');
  5627. }
  5628. FB.ui(FB.copy({
  5629. method: 'permissions.oauth',
  5630. display: 'popup',
  5631. domain: location.hostname
  5632. }, opts || {}),
  5633. cb);
  5634. },
  5635. /**
  5636. * Logout the user in the background.
  5637. *
  5638. * Just like logging in is tied to facebook.com, so is logging out -- and
  5639. * this call logs the user out of both Facebook and your site. This is a
  5640. * simple call:
  5641. *
  5642. * FB.logout(function(response) {
  5643. * // user is now logged out
  5644. * });
  5645. *
  5646. * NOTE: You can only log out a user that is connected to your site.
  5647. *
  5648. * @access public
  5649. * @param cb {Function} The callback function.
  5650. */
  5651. logout: function(cb) {
  5652. FB.ui({ method: 'auth.logout', display: 'hidden' }, cb);
  5653. }
  5654. });
  5655. /**
  5656. * Internal Authentication implementation.
  5657. *
  5658. * @class FB.Auth
  5659. * @static
  5660. * @access private
  5661. */
  5662. FB.provide('Auth', {
  5663. // pending callbacks for FB.getLoginStatus() calls
  5664. _callbacks: [],
  5665. _xdStorePath: 'xd_localstorage/',
  5666. /**
  5667. * Fetch a fresh login status from the server. This should not ordinarily
  5668. * be called directly; use FB.getLoginStatus instead.
  5669. */
  5670. fetchLoginStatus: function(lsCb) {
  5671. // CORDOVA PATCH
  5672. if (FB.UA.mobile() && window.postMessage && window.localStorage && !FB._nativeInterface) {
  5673. FB.Auth.staticAuthCheck(lsCb);
  5674. } else {
  5675. FB.ui({
  5676. method: 'login.status',
  5677. display: 'none',
  5678. domain: location.hostname
  5679. },
  5680. lsCb
  5681. );
  5682. }
  5683. },
  5684. /**
  5685. * Perform auth check using static endpoint first, then use
  5686. * login_status as backup when static endpoint does not fetch any
  5687. * results.
  5688. */
  5689. staticAuthCheck: function(lsCb) {
  5690. var domain = FB.getDomain('https_staticfb');
  5691. FB.Content.insertIframe({
  5692. root: FB.Content.appendHidden(''),
  5693. className: 'FB_UI_Hidden',
  5694. url: domain + FB.Auth._xdStorePath,
  5695. onload: function(iframe) {
  5696. var server = frames[iframe.name];
  5697. var guid = FB.guid();
  5698. var handled = false;
  5699. var fn = function(response) {
  5700. if (!handled) {
  5701. handled = true;
  5702. FB.Auth._staticAuthHandler(lsCb, response);
  5703. }
  5704. };
  5705. FB.XD.handler(fn, 'parent', true, guid);
  5706. // In case the static handler doesn't respond in time, we use
  5707. // a timer to trigger a response.
  5708. setTimeout(fn, 500);
  5709. server.postMessage(
  5710. FB.JSON.stringify({
  5711. method: 'getItem',
  5712. params: ['LoginInfo_' + FB._apiKey, /* do_log */ true],
  5713. returnCb: guid
  5714. }),
  5715. domain);
  5716. }
  5717. });
  5718. },
  5719. _staticAuthHandler: function(cb, response) {
  5720. if (response && response.data && response.data.status &&
  5721. response.data.status == 'connected') {
  5722. var r;
  5723. var status = response.data.status;
  5724. if (response.data.https == 1) {
  5725. FB._https = true;
  5726. }
  5727. var authResponse = response.data.authResponse || null;
  5728. r = FB.Auth.setAuthResponse(authResponse, status);
  5729. cb && cb(r);
  5730. } else {
  5731. // finally make the call to login status
  5732. FB.ui({ method: 'login.status', display: 'none' }, cb);
  5733. }
  5734. },
  5735. /**
  5736. * Sets new access token and user status values. Invokes all the registered
  5737. * subscribers if needed.
  5738. *
  5739. * @access private
  5740. * @param authResponse {Object} the new auth response surrouning the access
  5741. * token, user id, signed request, and expiry
  5742. * time.
  5743. * @param status {String} the new status
  5744. * @return {Object} the "response" object, which is a simple
  5745. * dictionary object surrounding the two
  5746. * incoming values.
  5747. */
  5748. setAuthResponse: function(authResponse, status) {
  5749. var userID = 0;
  5750. if (authResponse) {
  5751. // if there's an auth record, then there are a few ways we might
  5752. // actually get a user ID out of it. If an explcit user ID is provided,
  5753. // then go with that. If there's no explicit user ID, but there's a valid
  5754. // signed request with a user ID inside, then use that as a backup.
  5755. if (authResponse.userID) {
  5756. userID = authResponse.userID;
  5757. } else if (authResponse.signedRequest) {
  5758. var parsedSignedRequest =
  5759. FB.Auth.parseSignedRequest(authResponse.signedRequest);
  5760. if (parsedSignedRequest && parsedSignedRequest.user_id) {
  5761. userID = parsedSignedRequest.user_id;
  5762. }
  5763. }
  5764. }
  5765. var
  5766. login = !FB._userID && authResponse,
  5767. logout = FB._userID && !authResponse,
  5768. both = authResponse && FB._userID != userID,
  5769. authResponseChange = login || logout || both,
  5770. statusChange = status != FB._userStatus;
  5771. var response = {
  5772. authResponse : authResponse,
  5773. status : status
  5774. };
  5775. FB._authResponse = authResponse;
  5776. FB._userID = userID;
  5777. FB._userStatus = status;
  5778. if (logout || both) {
  5779. FB.Event.fire('auth.logout', response);
  5780. }
  5781. if (login || both) {
  5782. FB.Event.fire('auth.login', response);
  5783. }
  5784. if (authResponseChange) {
  5785. FB.Event.fire('auth.authResponseChange', response);
  5786. }
  5787. if (statusChange) {
  5788. FB.Event.fire('auth.statusChange', response);
  5789. }
  5790. // re-setup a timer to refresh the authResponse if needed. we only do this
  5791. // if FB.Auth._loadState exists, indicating that the application relies on
  5792. // the JS to get and refresh authResponse information
  5793. // (vs managing it themselves).
  5794. if (FB.Auth._refreshTimer) {
  5795. window.clearTimeout(FB.Auth._refreshTimer);
  5796. delete FB.Auth._refreshTimer;
  5797. }
  5798. if (FB.Auth._loadState && authResponse) {
  5799. FB.Auth._refreshTimer = window.setTimeout(function() {
  5800. FB.getLoginStatus(null, true); // force refresh
  5801. }, 1200000); // 20 minutes
  5802. }
  5803. return response;
  5804. },
  5805. _getContextType: function() {
  5806. // Set session origin
  5807. // WEB = 1
  5808. // MOBILE_CANVAS = 2
  5809. // NATIVE_MOBILE = 3
  5810. // DESKTOP = 4
  5811. // WEB_CANVAS = 5
  5812. if (FB.UA.nativeApp()) {
  5813. return 3;
  5814. }
  5815. if (FB.UA.mobile()) {
  5816. return 2;
  5817. }
  5818. if (FB._inCanvas) {
  5819. return 5;
  5820. }
  5821. return 1;
  5822. },
  5823. /**
  5824. * This handles receiving an access token from:
  5825. * - /dialog/oauth
  5826. *
  5827. * Whenever a user is logged in and has connected to the application, the
  5828. * params passed to the supplied callback include:
  5829. *
  5830. * {
  5831. * access_token: an access token
  5832. * expires_in: the number of seconds before the access token expires
  5833. * code: the authorization code used to generate
  5834. * signed_request: the code/user_id cookie, provided if and only if
  5835. * cookies are enabled.
  5836. * }
  5837. *
  5838. * If the user is logged out, or if the user is logged in and not connected,
  5839. * then the callback gets a smaller param record that includes:
  5840. *
  5841. * {
  5842. * error: either 'not_authorized' or 'unknown'
  5843. * }
  5844. *
  5845. * @access private
  5846. * @param cb {Function} the callback function.
  5847. * @param frame {String} the frame id the callback is tied to.
  5848. * @param target {String} 'parent' or 'opener' to indicate window
  5849. * relation.
  5850. * @param authResponse {Object} backup access token record, if not
  5851. * found in response.
  5852. * @param method {String} the name of the method invoking this
  5853. * @return {String} the xd url bound to the callback
  5854. */
  5855. xdHandler: function(cb, frame, target, authResponse, method) {
  5856. return FB.UIServer._xdNextHandler(
  5857. FB.Auth.xdResponseWrapper(cb, authResponse, method),
  5858. frame,
  5859. target,
  5860. true);
  5861. },
  5862. /**
  5863. * This handles receiving an access token from:
  5864. * - /dialog/oauth
  5865. *
  5866. * It updates the internal SDK access token record based on the response
  5867. * and invokes the (optional) user specified callback.
  5868. *
  5869. * Whenever a user is logged in and has connected to the application, the
  5870. * callback gets the following passed to it:
  5871. *
  5872. * {
  5873. * access_token: an access token
  5874. * expires_in: the number of seconds before the access token expires
  5875. * code: the authorization code used to generate
  5876. * signed_request: the code/user_id cookie, provided if and only if
  5877. * cookies are enabled.
  5878. * }
  5879. *
  5880. * If the user is logged out, or if the user is logged in and not connected,
  5881. * then the callback gets a smaller param record that includes:
  5882. *
  5883. * {
  5884. * error: either 'not_authorized' or 'unknown'
  5885. * }
  5886. *
  5887. * @access private
  5888. * @param cb {Function} the callback function
  5889. * @param status {String} the connect status this handler will
  5890. * trigger
  5891. * @param authResponse {Object} backup access token record, if none
  5892. * is found in response
  5893. * @param method {String} the name of the method invoking this
  5894. * @return {Function} the wrapped xd handler function
  5895. */
  5896. xdResponseWrapper: function(cb, authResponse, method) {
  5897. return function(params) {
  5898. if (params.access_token) {
  5899. // Whatever this is a response to, it succeeded
  5900. var parsedSignedRequest =
  5901. FB.Auth.parseSignedRequest(params.signed_request);
  5902. authResponse = {
  5903. accessToken: params.access_token,
  5904. userID: parsedSignedRequest.user_id,
  5905. expiresIn: parseInt(params.expires_in, 10),
  5906. signedRequest: params.signed_request
  5907. };
  5908. if (FB.Cookie.getEnabled()) {
  5909. var expirationTime = authResponse.expiresIn === 0
  5910. ? 0 // make this a session cookie if it's for offline access
  5911. : (new Date()).getTime() + authResponse.expiresIn * 1000;
  5912. var baseDomain = FB.Cookie._domain;
  5913. if (!baseDomain && params.base_domain) {
  5914. // if no base domain was set, and we got a base domain back
  5915. // from the our side, lets use this and prepend . to also
  5916. // cover subdomains (this will actually be added anyway by
  5917. // the browser).
  5918. baseDomain = '.' + params.base_domain;
  5919. }
  5920. FB.Cookie.setSignedRequestCookie(params.signed_request,
  5921. expirationTime,
  5922. baseDomain);
  5923. }
  5924. FB.Auth.setAuthResponse(authResponse, 'connected');
  5925. } else if (!FB._authResponse && authResponse) {
  5926. // Should currently not be hit since authResponse is a copy of
  5927. // FB._authResponse
  5928. // use the cached version we had access to
  5929. FB.Auth.setAuthResponse(authResponse, 'connected');
  5930. } else if (!(authResponse && method == 'permissions.oauth')) {
  5931. // Do not enter this when we had an authResponse at the time
  5932. // of calling permissions.oauth, and no access_token was returned.
  5933. // This is the case when a TOSed app requests additional perms,
  5934. // but the user skips this.
  5935. var status;
  5936. if (params.error && params.error === 'not_authorized') {
  5937. status = 'not_authorized';
  5938. } else {
  5939. status = 'unknown';
  5940. }
  5941. FB.Auth.setAuthResponse(null, status);
  5942. if (FB.Cookie.getEnabled()) {
  5943. FB.Cookie.clearSignedRequestCookie();
  5944. }
  5945. }
  5946. // Use HTTPS for future requests.
  5947. if (params && params.https == 1 && !FB._https) {
  5948. FB._https = true;
  5949. }
  5950. response = {
  5951. authResponse: FB._authResponse,
  5952. status: FB._userStatus
  5953. };
  5954. cb && cb(response);
  5955. };
  5956. },
  5957. /**
  5958. * Discards the signature part of the signed request
  5959. * (we don't have the secret used to sign it, and we can't
  5960. * expect developers to expose their secret here), and
  5961. * base64URL-decodes and json-decodes the payload portion
  5962. * to return a small dictionary around the authorization code
  5963. * and user id.
  5964. *
  5965. * @return {Object} small JS object housing an authorization
  5966. * code and the user id.
  5967. */
  5968. parseSignedRequest: function(signed_request) {
  5969. if (!signed_request) {
  5970. return null;
  5971. }
  5972. var boom = signed_request.split('.', 2);
  5973. // boom[0] is a signature that can't be verified here, because
  5974. // we don't (and shouldn't) have client side access to the app secret
  5975. var payload = boom[1];
  5976. var data = FB.Auth.base64URLDecode(payload);
  5977. return FB.JSON.parse(data);
  5978. },
  5979. /**
  5980. * Standard algorithm to decode a packet known to be encoded
  5981. * using the standard base64 encoding algorithm, save for the
  5982. * difference that the packet contains - where there would normally
  5983. * have been a +, and _ where there'd normally be a /.
  5984. *
  5985. * @param {String}
  5986. */
  5987. base64URLDecode: function(input) {
  5988. // +'s and /'s are replaced, by Facebook, with urlencode-safe
  5989. // characters - and _, respectively. We could just changed the
  5990. // key string, but better to clarify this and then go with the
  5991. // standard key string, in case this code gets lifted and dropped
  5992. // somewhere else.
  5993. input = input.replace(/\-/g, '+').replace(/\_/g, '/');
  5994. // our signed requests aren't automatically 0 mod 4 in length, so we
  5995. // need to pad with some '=' characters to round it out.
  5996. if (input.length % 4 !== 0) {
  5997. var padding = 4 - input.length % 4;
  5998. for (var d = 0; d < padding; d++) {
  5999. input = input + '=';
  6000. }
  6001. }
  6002. var keyStr =
  6003. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  6004. var output = "";
  6005. var chr1, chr2, chr3 = "";
  6006. var enc1, enc2, enc3, enc4 = "";
  6007. for (var i = 0; i < input.length; i += 4) {
  6008. enc1 = keyStr.indexOf(input.charAt(i));
  6009. enc2 = keyStr.indexOf(input.charAt(i + 1));
  6010. enc3 = keyStr.indexOf(input.charAt(i + 2));
  6011. enc4 = keyStr.indexOf(input.charAt(i + 3));
  6012. chr1 = (enc1 << 2) | (enc2 >> 4);
  6013. chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  6014. chr3 = ((enc3 & 3) << 6) | enc4;
  6015. output = output + String.fromCharCode(chr1);
  6016. if (enc3 != 64) {
  6017. output = output + String.fromCharCode(chr2);
  6018. }
  6019. if (enc4 != 64) {
  6020. output = output + String.fromCharCode(chr3);
  6021. }
  6022. chr1 = chr2 = chr3 = "";
  6023. enc1 = enc2 = enc3 = enc4 = "";
  6024. }
  6025. return unescape(output);
  6026. }
  6027. });
  6028. FB.provide('UIServer.Methods', {
  6029. 'permissions.oauth': {
  6030. url : 'dialog/oauth',
  6031. size : { width: (FB.UA.mobile() ? null : 627),
  6032. height: (FB.UA.mobile() ? null : 326) },
  6033. transform : function(call) {
  6034. if (!FB._apiKey) {
  6035. FB.log('FB.login() called before FB.init().');
  6036. return;
  6037. }
  6038. // if an access token is already available and no additional
  6039. // params are being requested (via a scope attribute within the params)
  6040. // then the callback should be pinged directly without the round trip.
  6041. if (FB._authResponse && !call.params.scope) {
  6042. FB.log('FB.login() called when user is already connected.');
  6043. call.cb && call.cb({ status: FB._userStatus,
  6044. authResponse: FB._authResponse });
  6045. return;
  6046. }
  6047. var
  6048. cb = call.cb,
  6049. id = call.id;
  6050. delete call.cb;
  6051. FB.copy(
  6052. call.params, {
  6053. client_id : FB._apiKey,
  6054. redirect_uri : FB.URI.resolve(
  6055. FB.Auth.xdHandler(
  6056. cb,
  6057. id,
  6058. 'opener',
  6059. FB._authResponse,
  6060. 'permissions.oauth')),
  6061. origin : FB.Auth._getContextType(),
  6062. response_type: 'token,signed_request',
  6063. domain: location.hostname
  6064. });
  6065. return call;
  6066. }
  6067. },
  6068. 'auth.logout': {
  6069. url : 'logout.php',
  6070. transform : function(call) {
  6071. if (!FB._apiKey) {
  6072. FB.log('FB.logout() called before calling FB.init().');
  6073. } else if (!FB._authResponse) {
  6074. FB.log('FB.logout() called without an access token.');
  6075. } else {
  6076. call.params.next = FB.Auth.xdHandler(call.cb,
  6077. call.id,
  6078. 'parent',
  6079. FB._authResponse);
  6080. return call;
  6081. }
  6082. }
  6083. },
  6084. 'login.status': {
  6085. url : 'dialog/oauth',
  6086. transform : function(call) {
  6087. var
  6088. cb = call.cb,
  6089. id = call.id;
  6090. delete call.cb;
  6091. FB.copy(call.params, {
  6092. client_id : FB._apiKey,
  6093. redirect_uri : FB.Auth.xdHandler(cb,
  6094. id,
  6095. 'parent',
  6096. FB._authResponse),
  6097. origin : FB.Auth._getContextType(),
  6098. response_type : 'token,signed_request,code',
  6099. domain: location.hostname
  6100. });
  6101. return call;
  6102. }
  6103. }
  6104. });
  6105. /**
  6106. * Copyright Facebook Inc.
  6107. *
  6108. * Licensed under the Apache License, Version 2.0 (the "License");
  6109. * you may not use this file except in compliance with the License.
  6110. * You may obtain a copy of the License at
  6111. *
  6112. * http://www.apache.org/licenses/LICENSE-2.0
  6113. *
  6114. * Unless required by applicable law or agreed to in writing, software
  6115. * distributed under the License is distributed on an "AS IS" BASIS,
  6116. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6117. * See the License for the specific language governing permissions and
  6118. * limitations under the License.
  6119. *
  6120. *
  6121. *
  6122. * Contains the public method ``FB.Insights.setDoneLoading`` for tracking
  6123. * application load times
  6124. *
  6125. * @provides fb.canvas.insights
  6126. * @requires fb.canvas
  6127. */
  6128. /**
  6129. * @class FB.CanvasInsights
  6130. * @static
  6131. * @access public
  6132. */
  6133. FB.provide('CanvasInsights', {
  6134. /**
  6135. * Deprecated - use FB.Canvas.setDoneLoading
  6136. */
  6137. setDoneLoading : function(callback) {
  6138. FB.Canvas.setDoneLoading(callback);
  6139. }
  6140. });
  6141. /**
  6142. * Copyright Facebook Inc.
  6143. *
  6144. * Licensed under the Apache License, Version 2.0 (the "License");
  6145. * you may not use this file except in compliance with the License.
  6146. * You may obtain a copy of the License at
  6147. *
  6148. * http://www.apache.org/licenses/LICENSE-2.0
  6149. *
  6150. * Unless required by applicable law or agreed to in writing, software
  6151. * distributed under the License is distributed on an "AS IS" BASIS,
  6152. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6153. * See the License for the specific language governing permissions and
  6154. * limitations under the License.
  6155. *
  6156. *
  6157. *
  6158. * @provides fb.cookie
  6159. * @requires fb.prelude
  6160. * fb.qs
  6161. * fb.event
  6162. */
  6163. /**
  6164. * Cookie Support.
  6165. *
  6166. * @class FB.Cookie
  6167. * @static
  6168. * @access private
  6169. */
  6170. FB.provide('Cookie', {
  6171. /**
  6172. * Holds the base_domain property to match the Cookie domain.
  6173. *
  6174. * @access private
  6175. * @type String
  6176. */
  6177. _domain: null,
  6178. /**
  6179. * Indicate if Cookie support should be enabled.
  6180. *
  6181. * @access private
  6182. * @type Boolean
  6183. */
  6184. _enabled: false,
  6185. /**
  6186. * Enable or disable Cookie support.
  6187. *
  6188. * @access private
  6189. * @param val {Boolean} true to enable, false to disable
  6190. */
  6191. setEnabled: function(val) {
  6192. FB.Cookie._enabled = !!val;
  6193. if (typeof val == 'string') {
  6194. FB.Cookie._domain = val;
  6195. }
  6196. },
  6197. /**
  6198. * Return the current status of the cookie system.
  6199. *
  6200. * @access private
  6201. * @returns {Boolean} true if Cookie support is enabled
  6202. */
  6203. getEnabled: function() {
  6204. return FB.Cookie._enabled;
  6205. },
  6206. /**
  6207. * Try loading metadata from the unsecure fbm_ cookie
  6208. *
  6209. * @access private
  6210. * @return {Object} the meta data for for the connect implementation
  6211. */
  6212. loadMeta: function() {
  6213. var
  6214. // note, we have the opening quote for the value in the regex, but do
  6215. // not have a closing quote. this is because the \b already handles it.
  6216. cookie = document.cookie.match('\\bfbm_' + FB._apiKey + '=([^;]*)\\b'),
  6217. meta;
  6218. if (cookie) {
  6219. // url encoded session stored as "sub-cookies"
  6220. meta = FB.QS.decode(cookie[1]);
  6221. if (!FB.Cookie._domain) {
  6222. // capture base_domain for use when we need to clear
  6223. FB.Cookie._domain = meta.base_domain;
  6224. }
  6225. }
  6226. return meta;
  6227. },
  6228. /**
  6229. * Try loading the signedRequest from the cookie if one is found.
  6230. *
  6231. * @return {String} the cached signed request, or null if one can't be found.
  6232. */
  6233. loadSignedRequest: function() {
  6234. var cookie =
  6235. document.cookie.match('\\bfbsr_' + FB._apiKey + '=([^;]*)\\b');
  6236. if (!cookie) {
  6237. return null;
  6238. }
  6239. return cookie[1];
  6240. },
  6241. /**
  6242. * Set the signed request cookie to something nonempty
  6243. * and without expiration time, or clear it if the cookie is
  6244. * missing or empty.
  6245. *
  6246. * @access private
  6247. * @param {String} signed_request_cookie the code/user_id cookie
  6248. * in signed request format.
  6249. * @param {Integer} The time at which the cookie should expire.
  6250. * @param {String} The domain for which this cookie should be set.
  6251. */
  6252. setSignedRequestCookie: function(signed_request_cookie, expiration_time,
  6253. base_domain) {
  6254. if (!signed_request_cookie) {
  6255. throw new Error('Value passed to FB.Cookie.setSignedRequestCookie ' +
  6256. 'was empty.');
  6257. }
  6258. if (!FB.Cookie.getEnabled()) {
  6259. return;
  6260. }
  6261. if (base_domain) {
  6262. // store this so that we can use it when deleting the cookie
  6263. var meta = FB.QS.encode({
  6264. base_domain: base_domain
  6265. });
  6266. FB.Cookie.setRaw('fbm_', meta, expiration_time, base_domain);
  6267. }
  6268. FB.Cookie._domain = base_domain;
  6269. FB.Cookie.setRaw('fbsr_', signed_request_cookie, expiration_time,
  6270. base_domain);
  6271. },
  6272. /**
  6273. * Clears the signed request cookie normally set by
  6274. * setSignedRequestCookie above.
  6275. */
  6276. clearSignedRequestCookie: function() {
  6277. if (!FB.Cookie.getEnabled()) {
  6278. return;
  6279. }
  6280. FB.Cookie.setRaw('fbsr_', '', 0, FB.Cookie._domain);
  6281. },
  6282. /**
  6283. * Helper function to set cookie value.
  6284. *
  6285. * @access private
  6286. * @param prefix {String} short string namespacing the cookie
  6287. * @param val {String} the string value (should already be encoded)
  6288. * @param ts {Number} a unix timestamp denoting expiration
  6289. * @param domain {String} optional domain for cookie
  6290. */
  6291. setRaw: function(prefix, val, ts, domain) {
  6292. // Start by clearing potentially overlapping cookies
  6293. if (domain) {
  6294. // No domain set (will become example.com)
  6295. document.cookie =
  6296. prefix + FB._apiKey + '=; expires=Wed, 04 Feb 2004 08:00:00 GMT;';
  6297. // This domain, (will become .example.com)
  6298. document.cookie =
  6299. prefix + FB._apiKey + '=; expires=Wed, 04 Feb 2004 08:00:00 GMT;' +
  6300. 'domain=' + location.hostname + ';';
  6301. }
  6302. var expires = new Date(ts).toGMTString();
  6303. document.cookie =
  6304. prefix + FB._apiKey + '=' + val +
  6305. (val && ts === 0 ? '' : '; expires=' + expires) +
  6306. '; path=/' +
  6307. (domain ? '; domain=' + domain : '');
  6308. }
  6309. });
  6310. /**
  6311. * Copyright Facebook Inc.
  6312. *
  6313. * Licensed under the Apache License, Version 2.0 (the "License");
  6314. * you may not use this file except in compliance with the License.
  6315. * You may obtain a copy of the License at
  6316. *
  6317. * http://www.apache.org/licenses/LICENSE-2.0
  6318. *
  6319. * Unless required by applicable law or agreed to in writing, software
  6320. * distributed under the License is distributed on an "AS IS" BASIS,
  6321. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6322. * See the License for the specific language governing permissions and
  6323. * limitations under the License.
  6324. *
  6325. * @provides fb.frictionless
  6326. * @requires fb.prelude
  6327. * fb.api
  6328. * fb.array
  6329. * fb.auth
  6330. * fb.event
  6331. * fb.string
  6332. */
  6333. /**
  6334. * Frictionless request recipient list management.
  6335. *
  6336. * @class FB.Frictionless
  6337. * @static
  6338. * @private
  6339. */
  6340. FB.provide('Frictionless', {
  6341. // mapping of user id to boolean indicating whether that recipient can receive
  6342. // frictionless requests
  6343. _allowedRecipients: {},
  6344. _useFrictionless: false,
  6345. /**
  6346. * Requests the frictionless request recipient list via a graph api call.
  6347. */
  6348. _updateRecipients: function() {
  6349. FB.Frictionless._allowedRecipients = {};
  6350. FB.api('/me/apprequestformerrecipients', function(response) {
  6351. if (!response || response.error) {
  6352. return;
  6353. }
  6354. FB.Array.forEach(response.data, function(recipient) {
  6355. FB.Frictionless._allowedRecipients[recipient.recipient_id] = true;
  6356. }, false);
  6357. });
  6358. },
  6359. /**
  6360. * Subscribes to login event and updates recipients when it fire.
  6361. */
  6362. init: function() {
  6363. FB.Frictionless._useFrictionless = true;
  6364. FB.getLoginStatus(function(response) {
  6365. if (response.status == 'connected') {
  6366. FB.Frictionless._updateRecipients();
  6367. }
  6368. });
  6369. FB.Event.subscribe('auth.login', function(login) {
  6370. if (login.authResponse) {
  6371. FB.Frictionless._updateRecipients();
  6372. }
  6373. });
  6374. },
  6375. /**
  6376. * Returns a callback function wrapper that updates the recipient list
  6377. * before calling the wrapped user callback
  6378. *
  6379. * @param cb {Function} the user callback function to wrap
  6380. * @return {Function} the wrapped callback function
  6381. */
  6382. _processRequestResponse: function(cb, hidden) {
  6383. return function(params) {
  6384. var updated = params && params.updated_frictionless;
  6385. if (FB.Frictionless._useFrictionless && updated) {
  6386. // only update the recipient list if the request dialog was shown and
  6387. // this is a frictionless app
  6388. FB.Frictionless._updateRecipients();
  6389. }
  6390. if (params) {
  6391. if (!hidden && params.frictionless) {
  6392. FB.Dialog._hideLoader();
  6393. FB.Dialog._restoreBodyPosition();
  6394. FB.Dialog._hideIPadOverlay();
  6395. }
  6396. delete params.frictionless;
  6397. delete params.updated_frictionless;
  6398. }
  6399. // call user callback
  6400. cb && cb(params);
  6401. };
  6402. },
  6403. /**
  6404. * Checks if a set of user ids are all in the frictionless request recipient
  6405. * list. Handles number, string, and array inputs.
  6406. *
  6407. * @param user_ids {String|Number|Array} the user ids to test
  6408. * @return {Boolean} whether all user ids allow frictionless requests
  6409. */
  6410. isAllowed: function(user_ids) {
  6411. if (!user_ids) {
  6412. return false;
  6413. }
  6414. if (typeof user_ids === 'number') {
  6415. return FB.Frictionless._allowedRecipients[user_ids];
  6416. }
  6417. if (typeof user_ids === 'string') {
  6418. user_ids = user_ids.split(',');
  6419. }
  6420. user_ids = FB.Array.map(user_ids, FB.String.trim);
  6421. var allowed = true;
  6422. var has_user_ids = false;
  6423. FB.Array.forEach(user_ids, function(user_id) {
  6424. allowed = allowed && FB.Frictionless._allowedRecipients[user_id];
  6425. has_user_ids = true;
  6426. }, false);
  6427. return allowed && has_user_ids;
  6428. }
  6429. });
  6430. /**
  6431. * Copyright Facebook Inc.
  6432. *
  6433. * Licensed under the Apache License, Version 2.0 (the "License");
  6434. * you may not use this file except in compliance with the License.
  6435. * You may obtain a copy of the License at
  6436. *
  6437. * http://www.apache.org/licenses/LICENSE-2.0
  6438. *
  6439. * Unless required by applicable law or agreed to in writing, software
  6440. * distributed under the License is distributed on an "AS IS" BASIS,
  6441. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6442. * See the License for the specific language governing permissions and
  6443. * limitations under the License.
  6444. *
  6445. *
  6446. *
  6447. * JavaScript library providing Facebook Connect integration.
  6448. *
  6449. * @provides fb.canvas.prefetcher
  6450. * @requires fb.array
  6451. * fb.init
  6452. * fb.json
  6453. * fb.prelude
  6454. */
  6455. /**
  6456. * This class samples applications' resources and uploads them to Facebook so
  6457. * that they may be flushed early if they are used frequently enough.
  6458. * @class FB.Canvas.Prefetcher
  6459. * @static
  6460. * @access public
  6461. */
  6462. FB.provide('Canvas.Prefetcher', {
  6463. _sampleRate : 0,
  6464. _appIdsBlacklist : [],
  6465. _links : [],
  6466. COLLECT_AUTOMATIC : 0,
  6467. COLLECT_MANUAL : 1,
  6468. _collectionMode : 0, // COLLECT_AUTOMATIC
  6469. /**
  6470. * Add a url for a resource that was used on this page load, but which did not
  6471. * appear in the DOM (and thus Facebook could not detect it automatically).
  6472. */
  6473. addStaticResource: function(url) {
  6474. if (!FB._inCanvas || !FB._apiKey) {
  6475. return;
  6476. }
  6477. FB.Canvas.Prefetcher._links.push(url);
  6478. },
  6479. /**
  6480. * Set collection mode:
  6481. * FB.Canvas.Prefetcher.COLLECT_AUTOMATIC (default) -
  6482. * FB automatically scrapes your application's DOM to
  6483. * determine which resources to report. It will pick the top several.
  6484. * You can also call addResource() to inform FB of resources it missed.
  6485. * FB.Canvas.Prefetcher.COLLECT_MANUAL -
  6486. * FB does not automatically scrape your application. Use this
  6487. * mode to completely control which resources are reported. Also, you
  6488. * can use it to turn off early flush, or turn it off for certain page
  6489. * loads that you don't want to affect the statistics.
  6490. *
  6491. * Returns: true on success.
  6492. */
  6493. setCollectionMode: function(mode) {
  6494. if (!FB._inCanvas || !FB._apiKey) {
  6495. return false;
  6496. }
  6497. if (mode != FB.Canvas.Prefetcher.COLLECT_AUTOMATIC &&
  6498. mode != FB.Canvas.Prefetcher.COLLECT_MANUAL) {
  6499. return false;
  6500. }
  6501. FB.Canvas.Prefetcher._collectionMode = mode;
  6502. },
  6503. _maybeSample : function() {
  6504. if (!FB._inCanvas || !FB._apiKey || !FB.Canvas.Prefetcher._sampleRate) {
  6505. return;
  6506. }
  6507. var rand = Math.random();
  6508. if (rand > 1 / FB.Canvas.Prefetcher._sampleRate) {
  6509. return;
  6510. }
  6511. if (FB.Canvas.Prefetcher._appIdsBlacklist == '*') {
  6512. return;
  6513. }
  6514. if (FB.Array.indexOf(
  6515. FB.Canvas.Prefetcher._appIdsBlacklist,
  6516. parseInt(FB._apiKey, 10)) != -1) {
  6517. return;
  6518. }
  6519. // We are definitely taking a sample. Wait 30 seconds to take it.
  6520. window.setTimeout(FB.Canvas.Prefetcher._sample, 30000);
  6521. },
  6522. _sample : function() {
  6523. // For now, get some random tags. Will do a more complete job later.
  6524. var resourceFieldsByTag = {
  6525. object: 'data',
  6526. link: 'href',
  6527. script: 'src'
  6528. };
  6529. // Application wants control over what resources are flushed
  6530. if (FB.Canvas.Prefetcher._collectionMode ==
  6531. FB.Canvas.Prefetcher.COLLECT_AUTOMATIC) {
  6532. FB.Array.forEach(resourceFieldsByTag, function(propertyName, tagName) {
  6533. FB.Array.forEach(
  6534. window.document.getElementsByTagName(tagName), function(tag) {
  6535. if (tag[propertyName]) {
  6536. FB.Canvas.Prefetcher._links.push(tag[propertyName]);
  6537. }
  6538. });
  6539. });
  6540. }
  6541. // Hit API with a JSON array of links to resources
  6542. var payload = FB.JSON.stringify(FB.Canvas.Prefetcher._links);
  6543. FB.api(FB._apiKey + '/staticresources', 'post',
  6544. { urls: payload, is_https: FB._https });
  6545. FB.Canvas.Prefetcher._links = [];
  6546. }
  6547. });
  6548. /**
  6549. * Deprecated - use FB.Canvas.Prefetcher
  6550. * @class FB.Canvas.EarlyFlush
  6551. * @static
  6552. * @access public
  6553. */
  6554. FB.provide('Canvas.EarlyFlush', {
  6555. addResource: function(url) {
  6556. return FB.Canvas.Prefetcher.addStaticResource(url);
  6557. },
  6558. setCollectionMode: function(mode) {
  6559. return FB.Canvas.Prefetcher.setCollectionMode(mode);
  6560. }
  6561. });
  6562. /**
  6563. * Copyright Facebook Inc.
  6564. *
  6565. * Licensed under the Apache License, Version 2.0 (the "License");
  6566. * you may not use this file except in compliance with the License.
  6567. * You may obtain a copy of the License at
  6568. *
  6569. * http://www.apache.org/licenses/LICENSE-2.0
  6570. *
  6571. * Unless required by applicable law or agreed to in writing, software
  6572. * distributed under the License is distributed on an "AS IS" BASIS,
  6573. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6574. * See the License for the specific language governing permissions and
  6575. * limitations under the License.
  6576. *
  6577. *
  6578. *
  6579. * JavaScript library providing Facebook Connect integration.
  6580. *
  6581. * @provides fb.init
  6582. * @requires fb.prelude
  6583. * fb.auth
  6584. * fb.api
  6585. * fb.canvas
  6586. * fb.canvas.prefetcher
  6587. * fb.cookie
  6588. * fb.frictionless
  6589. * fb.ui
  6590. * fb.ua
  6591. * fb.xd
  6592. */
  6593. /**
  6594. * This is the top level for all the public APIs.
  6595. *
  6596. * @class FB
  6597. * @static
  6598. * @access public
  6599. */
  6600. FB.provide('', {
  6601. // set by CONNECT_FB_INIT_CONFIG
  6602. initSitevars : {},
  6603. /**
  6604. * Initialize the library.
  6605. *
  6606. * Typical initialization enabling all optional features:
  6607. *
  6608. * <div id="fb-root"></div>
  6609. * <script src="http://connect.facebook.net/en_US/all.js"></script>
  6610. * <script>
  6611. * FB.init({
  6612. * appId : 'YOUR APP ID',
  6613. * status : true, // check login status
  6614. * cookie : true, // cookies allow server access to signed_request
  6615. * xfbml : true // parse XFBML
  6616. * });
  6617. * </script>
  6618. *
  6619. * The best place to put this code is right before the closing
  6620. * `</body>` tag.
  6621. *
  6622. * ### Asynchronous Loading
  6623. *
  6624. * The library makes non-blocking loading of the script easy to use by
  6625. * providing the `fbAsyncInit` hook. If this global function is defined, it
  6626. * will be executed when the library is loaded:
  6627. *
  6628. * <div id="fb-root"></div>
  6629. * <script>
  6630. * window.fbAsyncInit = function() {
  6631. * FB.init({
  6632. * appId : 'YOUR APP ID',
  6633. * status : true, // check login status
  6634. * cookie : true, // cookies allow server access to signed_request
  6635. * xfbml : true // parse XFBML
  6636. * });
  6637. * };
  6638. *
  6639. * (function() {
  6640. * var e = document.createElement('script');
  6641. * e.src = document.location.protocol +
  6642. * '//connect.facebook.net/en_US/all.js';
  6643. * e.async = true;
  6644. * document.getElementById('fb-root').appendChild(e);
  6645. * }());
  6646. * </script>
  6647. *
  6648. * The best place to put the asynchronous version of the code is right after
  6649. * the opening `<body>` tag. This allows Facebook initialization to happen in
  6650. * parallel with the initialization on the rest of your page.
  6651. *
  6652. * ### Internationalization
  6653. *
  6654. * Facebook Connect features are available many locales. You can replace the
  6655. * `en_US` locale specifed above with one of the [supported Facebook
  6656. * Locales][locales]. For example, to load up the library and trigger dialogs,
  6657. * popups and plugins to be in Hindi (`hi_IN`), you can load the library from
  6658. * this URL:
  6659. *
  6660. * http://connect.facebook.net/hi_IN/all.js
  6661. *
  6662. * [locales]: http://wiki.developers.facebook.com/index.php/Facebook_Locales
  6663. *
  6664. * ### SSL
  6665. *
  6666. * Facebook Connect is also available over SSL. You should only use this when
  6667. * your own page is served over `https://`. The library will rely on the
  6668. * current page protocol at runtime. The SSL URL is the same, only the
  6669. * protocol is changed:
  6670. *
  6671. * https://connect.facebook.net/en_US/all.js
  6672. *
  6673. * **Note**: Some [UI methods][FB.ui] like **stream.publish** and
  6674. * **stream.share** can be used without registering an application or calling
  6675. * this method. If you are using an appId, all methods **must** be called
  6676. * after this method.
  6677. *
  6678. * [FB.ui]: /docs/reference/javascript/FB.ui
  6679. *
  6680. * @access public
  6681. * @param options {Object}
  6682. *
  6683. * Property | Type | Description | Argument | Default
  6684. * -------------------- | ------- | ------------------------------------ | ---------- | -------
  6685. * appId | String | Your application ID. | *Optional* | `null`
  6686. * cookie | Boolean | `true` to enable cookie support. | *Optional* | `false`
  6687. * logging | Boolean | `false` to disable logging. | *Optional* | `true`
  6688. * status | Boolean | `false` to disable status ping. | *Optional* | `true`
  6689. * xfbml | Boolean | `true` to parse [[wiki:XFBML]] tags. | *Optional* | `false`
  6690. * useCachedDialogs | Boolean | `false` to disable cached dialogs | *Optional* | `true`
  6691. * frictionlessRequests | Boolean | `true` to enable frictionless requests | *Optional* | `false`
  6692. * authResponse | Object | Use specified access token record | *Optional* | `null`
  6693. * hideFlashCallback | function | (Canvas Only) callback for each flash element when popups overlay the page | *Optional* | `null`
  6694. */
  6695. init: function(options) {
  6696. // only need to list values here that do not already have a falsy default.
  6697. // this is why cookie/authResponse are not listed here.
  6698. options = FB.copy(options || {}, {
  6699. logging: true,
  6700. status: true
  6701. });
  6702. FB._userID = 0; // assume unknown or disconnected unless proved otherwise
  6703. FB._apiKey = options.appId || options.apiKey;
  6704. // CORDOVA PATCH
  6705. // if nativeInterface is specified then fire off the native initialization as well.
  6706. FB._nativeInterface = options.nativeInterface;
  6707. if (FB._nativeInterface) {
  6708. FB._nativeInterface.init(FB._apiKey, function(e) {alert('Cordova Facebook Connect plugin fail on init!');});
  6709. }
  6710. // disable logging if told to do so, but only if the url doesnt have the
  6711. // token to turn it on. this allows for easier debugging of third party
  6712. // sites even if logging has been turned off.
  6713. if (!options.logging &&
  6714. window.location.toString().indexOf('fb_debug=1') < 0) {
  6715. FB._logging = false;
  6716. }
  6717. FB.XD.init(options.channelUrl);
  6718. if (FB.UA.mobile() && FB.TemplateUI &&
  6719. FB.TemplateData && FB.TemplateData._enabled &&
  6720. options.useCachedDialogs !== false) {
  6721. FB.TemplateUI.init();
  6722. FB.Event.subscribe('auth.statusChange', FB.TemplateData.update);
  6723. }
  6724. if (options.reportTemplates) {
  6725. FB.reportTemplates = true;
  6726. }
  6727. if (options.frictionlessRequests) {
  6728. FB.Frictionless.init();
  6729. }
  6730. if (FB._apiKey) {
  6731. // enable cookie support if told to do so
  6732. FB.Cookie.setEnabled(options.cookie);
  6733. if (options.authResponse) {
  6734. FB.Auth.setAuthResponse(options.authResponse,
  6735. 'connected');
  6736. } else {
  6737. // we don't have an access token yet, but we might have a user
  6738. // ID based on a signed request in the cookie.
  6739. var signedRequest = FB.Cookie.loadSignedRequest();
  6740. var parsedSignedRequest = FB.Auth.parseSignedRequest(signedRequest);
  6741. FB._userID =
  6742. (parsedSignedRequest && parsedSignedRequest.user_id) || 0;
  6743. FB.Cookie.loadMeta();
  6744. }
  6745. // load a fresh authRequest (or access token) if requested
  6746. if (options.status) {
  6747. FB.getLoginStatus();
  6748. }
  6749. }
  6750. if (FB._inCanvas) {
  6751. FB.Canvas._setHideFlashCallback(options.hideFlashCallback);
  6752. FB.Canvas.init();
  6753. }
  6754. FB.Event.subscribe('xfbml.parse', function() {
  6755. FB.XFBML.IframeWidget.batchWidgetPipeRequests();
  6756. });
  6757. // weak dependency on XFBML
  6758. if (options.xfbml) {
  6759. // do this in a setTimeout to delay it until the current call stack has
  6760. // finished executing
  6761. window.setTimeout(function() {
  6762. if (FB.XFBML) {
  6763. if (FB.initSitevars.parseXFBMLBeforeDomReady) {
  6764. // poll to render new elements as fast as possible,
  6765. // without waiting for things like external js to load
  6766. FB.XFBML.parse();
  6767. var myI = window.setInterval(
  6768. function() {
  6769. FB.XFBML.parse();
  6770. },
  6771. 100);
  6772. FB.Dom.ready(
  6773. function() {
  6774. window.clearInterval(myI);
  6775. FB.XFBML.parse();
  6776. });
  6777. } else {
  6778. // traditional xfbml parse after dom is loaded
  6779. FB.Dom.ready(FB.XFBML.parse);
  6780. }
  6781. }
  6782. }, 0);
  6783. }
  6784. if (FB.Canvas && FB.Canvas.Prefetcher) {
  6785. FB.Canvas.Prefetcher._maybeSample();
  6786. }
  6787. }
  6788. });
  6789. /**
  6790. * Copyright Facebook Inc.
  6791. *
  6792. * Licensed under the Apache License, Version 2.0 (the "License");
  6793. * you may not use this file except in compliance with the License.
  6794. * You may obtain a copy of the License at
  6795. *
  6796. * http://www.apache.org/licenses/LICENSE-2.0
  6797. *
  6798. * Unless required by applicable law or agreed to in writing, software
  6799. * distributed under the License is distributed on an "AS IS" BASIS,
  6800. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6801. * See the License for the specific language governing permissions and
  6802. * limitations under the License.
  6803. *
  6804. * @provides fb.ui.methods
  6805. * @requires fb.prelude
  6806. * fb.ui
  6807. * fb.ua
  6808. */
  6809. /**
  6810. * Handler for UI dialogs that can be created as iframe dialog
  6811. * on mobile browser.
  6812. */
  6813. FB.provide('UIServer.MobileIframableMethod', {
  6814. transform: function(call) {
  6815. // For mobile display on mobile browsers that support
  6816. // postMessage, we use iframe dialog if the app has
  6817. // access token
  6818. if (call.params.display === 'touch' &&
  6819. call.params.access_token &&
  6820. window.postMessage
  6821. ) {
  6822. // Prepare for iframe by adding an channel parameter
  6823. // for resizing and add in_iframe parameter
  6824. call.params.channel = FB.UIServer._xdChannelHandler(
  6825. call.id,
  6826. 'parent'
  6827. );
  6828. // No iframe for modal dialogs in the native app
  6829. if (!FB.UA.nativeApp()) {
  6830. call.params.in_iframe = 1;
  6831. }
  6832. return call;
  6833. } else {
  6834. return FB.UIServer.genericTransform(call);
  6835. }
  6836. },
  6837. getXdRelation: function(params) {
  6838. var display = params.display;
  6839. if (display === 'touch' && window.postMessage && params.in_iframe) {
  6840. // mobile iframe directly use postMessage to communicate
  6841. // with parent window for resizing, so the xd relation should be
  6842. // parent instead of parent.parent.
  6843. return 'parent';
  6844. }
  6845. return FB.UIServer.getXdRelation(params);
  6846. }
  6847. });
  6848. /**
  6849. * Simple UI methods. Consider putting complex UI methods in their own modules.
  6850. */
  6851. FB.provide('UIServer.Methods', {
  6852. 'stream.share': {
  6853. size : { width: 650, height: 340 },
  6854. url : 'sharer.php',
  6855. transform : function(call) {
  6856. if (!call.params.u) {
  6857. call.params.u = window.location.toString();
  6858. }
  6859. return call;
  6860. }
  6861. },
  6862. 'fbml.dialog': {
  6863. size : { width: 575, height: 300 },
  6864. url : 'render_fbml.php',
  6865. loggedOutIframe : true,
  6866. // if we left transform blank, it would default to UI Server's transform
  6867. transform : function(call) { return call; }
  6868. },
  6869. // Just logs a user into Facebook and does NOT TOS the application
  6870. 'auth.logintofacebook': {
  6871. size : { width: 530, height: 287 },
  6872. url : 'login.php',
  6873. transform : function(call) {
  6874. // login.php will redirect you to uiserver if you have an api_key
  6875. // without this param
  6876. call.params.skip_api_login = 1;
  6877. // login.php won't let you put the XDhandler as your next url unless you
  6878. // are already logged in. After many attempts to do this with login.php,
  6879. // it is easier to log you in, then send you back to login.php with the
  6880. // next handler as the parameter. Feel free to do this in login.php
  6881. // instead, if you can figure it out.
  6882. var relation = FB.UIServer.getXdRelation(call.params);
  6883. var next = FB.UIServer._xdResult(
  6884. call.cb,
  6885. call.id,
  6886. relation,
  6887. true // isDefault
  6888. );
  6889. call.params.next = FB.getDomain(FB._https ? 'https_www' : 'www') +
  6890. "login.php?" + FB.QS.encode({
  6891. api_key: FB._apiKey,
  6892. next: next,
  6893. skip_api_login: 1
  6894. });
  6895. return call;
  6896. }
  6897. },
  6898. // Some extra stuff happens in FB.Frictionless.init() for this next one
  6899. 'apprequests': {
  6900. transform: function(call) {
  6901. call = FB.UIServer.MobileIframableMethod.transform(call);
  6902. call.params.frictionless = FB.Frictionless &&
  6903. FB.Frictionless._useFrictionless;
  6904. if (call.params.frictionless) {
  6905. if (FB.Frictionless.isAllowed(call.params.to)) {
  6906. // Always use iframe (instead of popup or new webview)
  6907. // for frictionless request that's already
  6908. // enabled for the current user and app because this action
  6909. // will be UI less.
  6910. call.params.in_iframe = true;
  6911. // hide load screen if this is a frictionless request
  6912. call.hideLoader = true;
  6913. }
  6914. // wrap user specified callback
  6915. call.cb = FB.Frictionless._processRequestResponse(
  6916. call.cb,
  6917. call.hideLoader
  6918. );
  6919. }
  6920. return call;
  6921. },
  6922. getXdRelation: function(params) {
  6923. return FB.UIServer.MobileIframableMethod.getXdRelation(params);
  6924. }
  6925. },
  6926. 'feed': FB.UIServer.MobileIframableMethod
  6927. });
  6928. /**
  6929. * Copyright Facebook Inc.
  6930. *
  6931. * Licensed under the Apache License, Version 2.0 (the "License");
  6932. * you may not use this file except in compliance with the License.
  6933. * You may obtain a copy of the License at
  6934. *
  6935. * http://www.apache.org/licenses/LICENSE-2.0
  6936. *
  6937. * Unless required by applicable law or agreed to in writing, software
  6938. * distributed under the License is distributed on an "AS IS" BASIS,
  6939. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  6940. * See the License for the specific language governing permissions and
  6941. * limitations under the License.
  6942. *
  6943. *
  6944. *
  6945. * @provides fb.compat.ui
  6946. * @requires fb.prelude
  6947. * fb.qs
  6948. * fb.ui
  6949. * fb.ui.methods
  6950. * fb.json
  6951. */
  6952. /**
  6953. * NOTE: You should use FB.ui() instead.
  6954. *
  6955. * UI Calls.
  6956. *
  6957. * @class FB
  6958. * @static
  6959. * @access private
  6960. */
  6961. FB.provide('', {
  6962. /**
  6963. * NOTE: You should use FB.ui() instead.
  6964. *
  6965. * Sharing is the light weight way of distributing your content. As opposed
  6966. * to the structured data explicitly given in the [FB.publish][publish] call,
  6967. * with share you simply provide the URL.
  6968. *
  6969. * FB.share('http://github.com/facebook/connect-js');
  6970. *
  6971. * Calling [FB.share][share] without any arguments will share the current
  6972. * page.
  6973. *
  6974. * This call can be used without requiring the user to sign in.
  6975. *
  6976. * [publish]: /docs/?u=facebook.jslib-alpha.FB.publish
  6977. * [share]: /docs/?u=facebook.jslib-alpha.FB.share
  6978. *
  6979. * @access private
  6980. * @param u {String} the url (defaults to current URL)
  6981. */
  6982. share: function(u) {
  6983. FB.log('FB.share() has been deprecated. Please use FB.ui() instead.');
  6984. FB.ui({
  6985. display : 'popup',
  6986. method : 'stream.share',
  6987. u : u
  6988. });
  6989. },
  6990. /**
  6991. * NOTE: You should use FB.ui() instead.
  6992. *
  6993. * Publish a post to the stream.
  6994. *
  6995. * This is the main, fully featured distribution mechanism for you
  6996. * to publish into the user's stream. It can be used, with or
  6997. * without an API key. With an API key you can control the
  6998. * Application Icon and get attribution. You must also do this if
  6999. * you wish to use the callback to get notified of the `post_id`
  7000. * and the `message` the user typed in the published post, or find
  7001. * out if the user did not publish (clicked on the skipped button).
  7002. *
  7003. * Publishing is a powerful feature that allows you to submit rich
  7004. * media and provide a integrated experience with control over your
  7005. * stream post. You can guide the user by choosing the prompt,
  7006. * and/or a default message which they may customize. In addition,
  7007. * you may provide image, video, audio or flash based attachments
  7008. * with along with their metadata. You also get the ability to
  7009. * provide action links which show next to the "Like" and "Comment"
  7010. * actions. All this together provides you full control over your
  7011. * stream post. In addition, if you may also specify a target for
  7012. * the story, such as another user or a page.
  7013. *
  7014. * A post may contain the following properties:
  7015. *
  7016. * Property | Type | Description
  7017. * ------------------- | ------ | --------------------------------------
  7018. * message | String | This allows prepopulating the message.
  7019. * attachment | Object | An [[wiki:Attachment (Streams)]] object.
  7020. * action_links | Array | An array of [[wiki:Action Links]].
  7021. * actor_id | String | A actor profile/page id.
  7022. * target_id | String | A target profile id.
  7023. * user_message_prompt | String | Custom prompt message.
  7024. *
  7025. * The post and all the parameters are optional, so use what is best
  7026. * for your specific case.
  7027. *
  7028. * Example:
  7029. *
  7030. * var post = {
  7031. * message: 'getting educated about Facebook Connect',
  7032. * attachment: {
  7033. * name: 'Facebook Connect JavaScript SDK',
  7034. * description: (
  7035. * 'A JavaScript library that allows you to harness ' +
  7036. * 'the power of Facebook, bringing the user\'s identity, ' +
  7037. * 'social graph and distribution power to your site.'
  7038. * ),
  7039. * href: 'http://github.com/facebook/connect-js'
  7040. * },
  7041. * action_links: [
  7042. * {
  7043. * text: 'GitHub Repo',
  7044. * href: 'http://github.com/facebook/connect-js'
  7045. * }
  7046. * ],
  7047. * user_message_prompt: 'Share your thoughts about Facebook Connect'
  7048. * };
  7049. *
  7050. * FB.publish(
  7051. * post,
  7052. * function(published_post) {
  7053. * if (published_post) {
  7054. * alert(
  7055. * 'The post was successfully published. ' +
  7056. * 'Post ID: ' + published_post.post_id +
  7057. * '. Message: ' + published_post.message
  7058. * );
  7059. * } else {
  7060. * alert('The post was not published.');
  7061. * }
  7062. * }
  7063. * );
  7064. *
  7065. * @access private
  7066. * @param post {Object} the post object
  7067. * @param cb {Function} called with the result of the action
  7068. */
  7069. publish: function(post, cb) {
  7070. FB.log('FB.publish() has been deprecated. Please use FB.ui() instead.');
  7071. post = post || {};
  7072. FB.ui(FB.copy({
  7073. display : 'popup',
  7074. method : 'stream.publish',
  7075. preview : 1
  7076. }, post || {}), cb);
  7077. },
  7078. /**
  7079. * NOTE: You should use FB.ui() instead.
  7080. *
  7081. * Prompt the user to add the given id as a friend.
  7082. *
  7083. * @access private
  7084. * @param id {String} the id of the target user
  7085. * @param cb {Function} called with the result of the action
  7086. */
  7087. addFriend: function(id, cb) {
  7088. FB.log('FB.addFriend() has been deprecated. Please use FB.ui() instead.');
  7089. FB.ui({
  7090. display : 'popup',
  7091. id : id,
  7092. method : 'friend.add'
  7093. }, cb);
  7094. }
  7095. });
  7096. // the "fake" UIServer method was called auth.login
  7097. FB.UIServer.Methods['auth.login'] = FB.UIServer.Methods['permissions.request'];
  7098. /**
  7099. * Copyright Facebook Inc.
  7100. *
  7101. * Licensed under the Apache License, Version 2.0 (the "License");
  7102. * you may not use this file except in compliance with the License.
  7103. * You may obtain a copy of the License at
  7104. *
  7105. * http://www.apache.org/licenses/LICENSE-2.0
  7106. *
  7107. * Unless required by applicable law or agreed to in writing, software
  7108. * distributed under the License is distributed on an "AS IS" BASIS,
  7109. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7110. * See the License for the specific language governing permissions and
  7111. * limitations under the License.
  7112. *
  7113. * @provides fb.xfbml
  7114. * @layer xfbml
  7115. * @requires fb.prelude
  7116. * fb.array
  7117. * fb.dom
  7118. * fb.ua
  7119. */
  7120. /**
  7121. * Methods for the rendering of [[wiki:XFBML]] tags.
  7122. *
  7123. * To render the tags, simply put the tags anywhere in your page, and then
  7124. * call:
  7125. *
  7126. * FB.XFBML.parse();
  7127. *
  7128. * @class FB.XFBML
  7129. * @static
  7130. */
  7131. FB.provide('XFBML', {
  7132. /**
  7133. * The time allowed for all tags to finish rendering.
  7134. *
  7135. * @type Number
  7136. */
  7137. _renderTimeout: 30000,
  7138. /**
  7139. * Finds elements that will become plugins.
  7140. * Looks for <fb:like> and <div class="fb-like">
  7141. *
  7142. * @param dom {DOMElement} the root DOM node
  7143. * @param xmlns {String} the XML namespace
  7144. * @param localName {String} the unqualified tag name
  7145. * @return {Array}
  7146. */
  7147. getElements: function(dom, xmlns, localName) {
  7148. var arr = FB.Array,
  7149. xfbmlDoms = FB.XFBML._getDomElements(dom, xmlns, localName),
  7150. html5Doms = FB.Dom.getByClass(xmlns + '-' + localName, dom, 'div');
  7151. xfbmlDoms = arr.toArray(xfbmlDoms);
  7152. html5Doms = arr.toArray(html5Doms);
  7153. // filter out html5 candidates that are not empty
  7154. // to prevent cases like <div class="fb-like"><fb:like></fb:like></div>
  7155. html5Doms = arr.filter(html5Doms, function(el) {
  7156. // let's throw it out unless
  7157. // the child is just one (1) empty text node (nodeType 3)
  7158. return !el.hasChildNodes() ||
  7159. (el.childNodes.length === 1 && el.childNodes[0].nodeType === 3);
  7160. });
  7161. return arr.merge(xfbmlDoms, html5Doms);
  7162. },
  7163. /**
  7164. * Parse and render XFBML markup in the document. XFBML enables you to
  7165. * incorporate [FBML](../fbml) into your websites and IFrame applications.
  7166. *
  7167. * You can parse the following XFBML tags with this method:
  7168. *
  7169. * * [fb:activity](../plugins/activity)
  7170. * * [fb:add-profile-tab](../fbml/add-profile-tab)
  7171. * * [fb:add-to-timeline](../plugins/add-to-timeline)
  7172. * * [fb:bookmark](../fbml/bookmark)
  7173. * * [fb:comments](../plugins/comments)
  7174. * * [fb:facepile](../plugins/facepile)
  7175. * * [fb:like](../plugins/like)
  7176. * * [fb:like-box](../plugins/like-box)
  7177. * * [fb:live-stream](../plugins/live-stream)
  7178. * * [fb:login-button](../plugins/login)
  7179. * * [fb:pronoun](../fbml/pronoun)
  7180. * * [fb:recommendations](../plugins/recommendations)
  7181. * * [fb:serverFbml](../fbml/serverFbml)
  7182. * * [fb:user-status](../fbml/user-status)
  7183. *
  7184. * Examples
  7185. * --------
  7186. *
  7187. * By default, this is all you need to make XFBML work:
  7188. *
  7189. * FB.XFBML.parse();
  7190. *
  7191. * Alternately, you may want to only evaluate a portion of
  7192. * the document. In that case, you can pass in the elment.
  7193. *
  7194. * FB.XFBML.parse(document.getElementById('foo'));
  7195. *
  7196. * @access public
  7197. * @param dom {DOMElement} (optional) root DOM node, defaults to body
  7198. * @param cb {Function} (optional) invoked when elements are rendered
  7199. */
  7200. parse: function(dom, cb) {
  7201. dom = dom || document.body;
  7202. // We register this function on each tag's "render" event. This allows us
  7203. // to invoke the callback when we're done rendering all the found elements.
  7204. //
  7205. // We start with count=1 rather than 0, and finally call onTagDone() after
  7206. // we've kicked off all the tag processing. This ensures that we do not hit
  7207. // count=0 before we're actually done queuing up all the tags.
  7208. var
  7209. count = 1,
  7210. onTagDone = function() {
  7211. count--;
  7212. if (count === 0) {
  7213. // Invoke the user specified callback for this specific parse() run.
  7214. cb && cb();
  7215. // Also fire a global event. A global event is fired for each
  7216. // invocation to FB.XFBML.parse().
  7217. FB.Event.fire('xfbml.render');
  7218. }
  7219. };
  7220. var cachedDomElements = {};
  7221. if (FB.XFBML._widgetPipeIsEnabled()) {
  7222. // first count the number of XFBML tags in the page that would benefit
  7223. // from a transition to use Widget Pipe. We do this, because we only
  7224. // want to engage widget pipe if the overhead is counterbalanced by the
  7225. // server-side generation time. And to make sure we don't scan for,
  7226. // say, all fb:like tags twice, we cache them so they can be referred
  7227. // during the loop that formally processes all of the tags.
  7228. FB.Array.forEach(FB.XFBML._tagInfos, function(tagInfo) {
  7229. if (tagInfo.supportsWidgetPipe) {
  7230. var xmlns = tagInfo.xmlns ? tagInfo.xmlns : 'fb';
  7231. var xfbmlDoms = FB.XFBML.getElements(dom, xmlns, tagInfo.localName);
  7232. cachedDomElements[tagInfo.localName] = xfbmlDoms;
  7233. FB.XFBML._widgetPipeEnabledTagCount += xfbmlDoms.length;
  7234. }
  7235. });
  7236. }
  7237. // First, find all tags that are present
  7238. FB.Array.forEach(FB.XFBML._tagInfos, function(tagInfo) {
  7239. // default the xmlns if needed
  7240. if (!tagInfo.xmlns) {
  7241. tagInfo.xmlns = 'fb';
  7242. }
  7243. var xfbmlDoms;
  7244. if (cachedDomElements[tagInfo.localName] !== undefined) {
  7245. xfbmlDoms = cachedDomElements[tagInfo.localName];
  7246. } else {
  7247. xfbmlDoms = FB.XFBML.getElements(
  7248. dom,
  7249. tagInfo.xmlns,
  7250. tagInfo.localName
  7251. );
  7252. }
  7253. for (var i=0; i < xfbmlDoms.length; i++) {
  7254. count++;
  7255. FB.XFBML._processElement(xfbmlDoms[i], tagInfo, onTagDone);
  7256. }
  7257. });
  7258. // Inform all tags that they've at least been processed, even if their
  7259. // content hasn't quite been fetched yet.
  7260. FB.Event.fire('xfbml.parse');
  7261. // Setup a timer to ensure all tags render within a given timeout
  7262. window.setTimeout(function() {
  7263. if (count > 0) {
  7264. FB.log(
  7265. count + ' XFBML tags failed to render in ' +
  7266. FB.XFBML._renderTimeout + 'ms.'
  7267. );
  7268. }
  7269. }, FB.XFBML._renderTimeout);
  7270. // Call once to handle count=1 as described above.
  7271. onTagDone();
  7272. },
  7273. /**
  7274. * Register a custom XFBML tag. If you create an custom XFBML tag, you can
  7275. * use this method to register it so the it can be treated like
  7276. * any build-in XFBML tags.
  7277. *
  7278. * Example
  7279. * -------
  7280. *
  7281. * Register fb:name tag that is implemented by class FB.XFBML.Name
  7282. * tagInfo = {xmlns: 'fb',
  7283. * localName: 'name',
  7284. * className: 'FB.XFBML.Name'},
  7285. * FB.XFBML.registerTag(tagInfo);
  7286. *
  7287. * @access private
  7288. * @param {Object} tagInfo
  7289. * an object containiner the following keys:
  7290. * - xmlns
  7291. * - localName
  7292. * - className
  7293. */
  7294. registerTag: function(tagInfo) {
  7295. FB.XFBML._tagInfos.push(tagInfo);
  7296. },
  7297. /**
  7298. * Decides on behalf of the entire document whether
  7299. * using WidgetPipe is even worth it.
  7300. *
  7301. * @return {Boolean} true if and only if the number of
  7302. * widget-pipe-compatible tags exceeds a certain
  7303. * threshold.
  7304. */
  7305. shouldUseWidgetPipe: function() {
  7306. if (!FB.XFBML._widgetPipeIsEnabled()) {
  7307. return false;
  7308. }
  7309. var aboveThreshold =
  7310. FB.XFBML._widgetPipeEnabledTagCount > 1;
  7311. return aboveThreshold;
  7312. },
  7313. /**
  7314. * Return a boolean value for a DOM attribute
  7315. *
  7316. * @param el {HTMLElement} DOM element
  7317. * @param attr {String} Attribute name
  7318. */
  7319. getBoolAttr: function(el, attr) {
  7320. attr = FB.XFBML.getAttr(el, attr);
  7321. return (attr && FB.Array.indexOf(
  7322. ['true', '1', 'yes', 'on'],
  7323. attr.toLowerCase()) > -1);
  7324. },
  7325. /**
  7326. * Return a value for a DOM attribute if exists
  7327. * Checks the attrib name verbatim as well as
  7328. * prepended with data-*
  7329. *
  7330. * @param el {HTMLElement} DOM element
  7331. * @param attr {String} Attribute name
  7332. */
  7333. getAttr: function(el, attr) {
  7334. return (
  7335. el.getAttribute(attr) ||
  7336. el.getAttribute(attr.replace(/_/g, '-')) ||
  7337. el.getAttribute(attr.replace(/-/g, '_')) ||
  7338. el.getAttribute(attr.replace(/-/g, '')) ||
  7339. el.getAttribute(attr.replace(/_/g, '')) ||
  7340. el.getAttribute('data-' + attr) ||
  7341. el.getAttribute('data-' + attr.replace(/_/g, '-')) ||
  7342. el.getAttribute('data-' + attr.replace(/-/g, '_')) ||
  7343. el.getAttribute('data-' + attr.replace(/-/g, '')) ||
  7344. el.getAttribute('data-' + attr.replace(/_/g, '')) ||
  7345. null
  7346. );
  7347. },
  7348. //////////////// Private methods ////////////////////////////////////////////
  7349. /**
  7350. * Process an XFBML element.
  7351. *
  7352. * @access private
  7353. * @param dom {DOMElement} the dom node
  7354. * @param tagInfo {Object} the tag information
  7355. * @param cb {Function} the function to bind to the "render" event for the tag
  7356. */
  7357. _processElement: function(dom, tagInfo, cb) {
  7358. // Check if element for the dom already exists
  7359. var element = dom._element;
  7360. if (element) {
  7361. element.subscribe('render', cb);
  7362. element.process();
  7363. } else {
  7364. var processor = function() {
  7365. var fn = eval(tagInfo.className);
  7366. // TODO(naitik) cleanup after f8
  7367. //
  7368. // currently, tag initialization is done via a constructor function,
  7369. // there by preventing a tag implementation to vary between two types
  7370. // of objects. post f8, this should be changed to a factory function
  7371. // which would allow the login button to instantiate the Button based
  7372. // tag or Iframe based tag depending on the attribute value.
  7373. var isLogin = false;
  7374. var showFaces = true;
  7375. var showLoginFace = false;
  7376. var renderInIframe = false;
  7377. var addToTimeline = (tagInfo.className === 'FB.XFBML.AddToTimeline');
  7378. if ((tagInfo.className === 'FB.XFBML.LoginButton') || addToTimeline) {
  7379. renderInIframe = FB.XFBML.getBoolAttr(dom, 'render-in-iframe');
  7380. mode = FB.XFBML.getAttr(dom, 'mode');
  7381. showFaces = (addToTimeline && mode != 'button') ||
  7382. FB.XFBML.getBoolAttr(dom, 'show-faces');
  7383. showLoginFace = FB.XFBML.getBoolAttr(dom, 'show-login-face');
  7384. isLogin = addToTimeline ||
  7385. renderInIframe ||
  7386. showFaces ||
  7387. showLoginFace ||
  7388. FB.XFBML.getBoolAttr(dom, 'oneclick');
  7389. if (isLogin && !addToTimeline) {
  7390. // override to be facepile-ish for an app id
  7391. fn = FB.XFBML.Login;
  7392. }
  7393. }
  7394. element = dom._element = new fn(dom);
  7395. if (isLogin) {
  7396. showFaces = !!showFaces;
  7397. showLoginFace = !!showLoginFace;
  7398. var extraParams = {show_faces: showFaces,
  7399. show_login_face: showLoginFace,
  7400. add_to_profile: addToTimeline,
  7401. mode: mode};
  7402. // For now we support both the perms and scope attribute
  7403. var scope = FB.XFBML.getAttr(dom, 'scope') ||
  7404. FB.XFBML.getAttr(dom, 'perms');
  7405. if (scope) {
  7406. extraParams.scope = scope;
  7407. }
  7408. element.setExtraParams(extraParams);
  7409. }
  7410. element.subscribe('render', cb);
  7411. element.process();
  7412. };
  7413. if (FB.CLASSES[tagInfo.className.substr(3)]) {
  7414. processor();
  7415. } else {
  7416. FB.log('Tag ' + tagInfo.className + ' was not found.');
  7417. }
  7418. }
  7419. },
  7420. /**
  7421. * Get all the DOM elements present under a given node with a given tag name.
  7422. *
  7423. * @access private
  7424. * @param dom {DOMElement} the root DOM node
  7425. * @param xmlns {String} the XML namespace
  7426. * @param localName {String} the unqualified tag name
  7427. * @return {DOMElementCollection}
  7428. */
  7429. _getDomElements: function(dom, xmlns, localName) {
  7430. // Different browsers behave slightly differently in handling tags
  7431. // with custom namespace.
  7432. var fullName = xmlns + ':' + localName;
  7433. if (FB.UA.firefox()) {
  7434. // Use document.body.namespaceURI as first parameter per
  7435. // suggestion by Firefox developers.
  7436. // See https://bugzilla.mozilla.org/show_bug.cgi?id=531662
  7437. return dom.getElementsByTagNameNS(document.body.namespaceURI, fullName);
  7438. } else if (FB.UA.ie() < 9) {
  7439. // accessing document.namespaces when the library is being loaded
  7440. // asynchronously can cause an error if the document is not yet ready
  7441. try {
  7442. var docNamespaces = document.namespaces;
  7443. if (docNamespaces && docNamespaces[xmlns]) {
  7444. var nodes = dom.getElementsByTagName(localName);
  7445. // if it's a modern browser, and we haven't found anything yet, we
  7446. // can try the fallback below, otherwise return whatever we found.
  7447. if (!document.addEventListener || nodes.length > 0) {
  7448. return nodes;
  7449. }
  7450. }
  7451. } catch (e) {
  7452. // introspection doesn't yield any identifiable information to scope
  7453. }
  7454. // It seems that developer tends to forget to declare the fb namespace
  7455. // in the HTML tag (xmlns:fb="http://ogp.me/ns/fb#") IE
  7456. // has a stricter implementation for custom tags. If namespace is
  7457. // missing, custom DOM dom does not appears to be fully functional. For
  7458. // example, setting innerHTML on it will fail.
  7459. //
  7460. // If a namespace is not declared, we can still find the element using
  7461. // GetElementssByTagName with namespace appended.
  7462. return dom.getElementsByTagName(fullName);
  7463. } else {
  7464. return dom.getElementsByTagName(fullName);
  7465. }
  7466. },
  7467. /**
  7468. * Register the default set of base tags. Each entry must have a localName
  7469. * and a className property, and can optionally have a xmlns property which
  7470. * if missing defaults to 'fb'.
  7471. *
  7472. * NOTE: Keep the list alpha sorted.
  7473. */
  7474. _tagInfos: [
  7475. { localName: 'activity', className: 'FB.XFBML.Activity' },
  7476. { localName: 'add-profile-tab', className: 'FB.XFBML.AddProfileTab' },
  7477. { localName: 'add-to-timeline', className: 'FB.XFBML.AddToTimeline' },
  7478. { localName: 'bookmark', className: 'FB.XFBML.Bookmark' },
  7479. { localName: 'comments', className: 'FB.XFBML.Comments' },
  7480. { localName: 'comments-count', className: 'FB.XFBML.CommentsCount' },
  7481. { localName: 'connect-bar', className: 'FB.XFBML.ConnectBar' },
  7482. { localName: 'fan', className: 'FB.XFBML.Fan' },
  7483. { localName: 'like', className: 'FB.XFBML.Like',
  7484. supportsWidgetPipe: true },
  7485. { localName: 'like-box', className: 'FB.XFBML.LikeBox' },
  7486. { localName: 'live-stream', className: 'FB.XFBML.LiveStream' },
  7487. { localName: 'login', className: 'FB.XFBML.Login' },
  7488. { localName: 'login-button', className: 'FB.XFBML.LoginButton' },
  7489. { localName: 'facepile', className: 'FB.XFBML.Facepile' },
  7490. { localName: 'friendpile', className: 'FB.XFBML.Friendpile' },
  7491. { localName: 'name', className: 'FB.XFBML.Name' },
  7492. { localName: 'profile-pic', className: 'FB.XFBML.ProfilePic' },
  7493. { localName: 'question', className: 'FB.XFBML.Question' },
  7494. { localName: 'recommendations', className: 'FB.XFBML.Recommendations' },
  7495. { localName: 'recommendations-bar',
  7496. className: 'FB.XFBML.RecommendationsBar' },
  7497. { localName: 'registration', className: 'FB.XFBML.Registration' },
  7498. { localName: 'send', className: 'FB.XFBML.Send' },
  7499. { localName: 'serverfbml', className: 'FB.XFBML.ServerFbml' },
  7500. { localName: 'share-button', className: 'FB.XFBML.ShareButton' },
  7501. { localName: 'social-context', className: 'FB.XFBML.SocialContext' },
  7502. { localName: 'subscribe', className: 'FB.XFBML.Subscribe' }
  7503. ],
  7504. // the number of widget-pipe-compatible tags we found in the DOM
  7505. _widgetPipeEnabledTagCount: 0,
  7506. /**
  7507. * Returns true if and only if we're willing to try out WidgetPipe
  7508. * in hopes of increasing plugin parallelization.
  7509. */
  7510. _widgetPipeIsEnabled: function() {
  7511. return FB.widgetPipeEnabledApps &&
  7512. FB.widgetPipeEnabledApps[FB._apiKey] !== undefined;
  7513. }
  7514. });
  7515. /*
  7516. * For IE, we will try to detect if document.namespaces contains 'fb' already
  7517. * and add it if it does not exist.
  7518. */
  7519. // wrap in a try/catch because it can throw an error if the library is loaded
  7520. // asynchronously and the document is not ready yet
  7521. (function() {
  7522. try {
  7523. if (document.namespaces && !document.namespaces.item.fb) {
  7524. document.namespaces.add('fb');
  7525. }
  7526. } catch(e) {
  7527. // introspection doesn't yield any identifiable information to scope
  7528. }
  7529. }());
  7530. /**
  7531. * Copyright Facebook Inc.
  7532. *
  7533. * Licensed under the Apache License, Version 2.0 (the "License");
  7534. * you may not use this file except in compliance with the License.
  7535. * You may obtain a copy of the License at
  7536. *
  7537. * http://www.apache.org/licenses/LICENSE-2.0
  7538. *
  7539. * Unless required by applicable law or agreed to in writing, software
  7540. * distributed under the License is distributed on an "AS IS" BASIS,
  7541. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7542. * See the License for the specific language governing permissions and
  7543. * limitations under the License.
  7544. *
  7545. * @provides fb.compat.xfbml
  7546. * @requires fb.prelude
  7547. * fb.xfbml
  7548. */
  7549. /**
  7550. * Methods for the rendering of [[wiki:XFBML]] tags.
  7551. *
  7552. * To render the tags, simply put the tags anywhere in your page, and then
  7553. * call:
  7554. *
  7555. * FB.XFBML.parse();
  7556. *
  7557. * @class FB.XFBML
  7558. * @static
  7559. */
  7560. FB.provide('XFBML', {
  7561. /**
  7562. * NOTE: This method is deprecated. You should instead set the innerHTML
  7563. * yourself and call FB.XFBML.parse() on the DOMElement after.
  7564. *
  7565. * Dynamically set XFBML markup on a given DOM element. Use this
  7566. * method if you want to set XFBML after the page has already loaded
  7567. * (for example, in response to an Ajax request or API call).
  7568. *
  7569. * Example:
  7570. * --------
  7571. * Set the innerHTML of a dom element with id "container"
  7572. * to some markup (fb:name + regular HTML) and render it
  7573. *
  7574. * FB.XFBML.set(FB.$('container'),
  7575. * '<fb:name uid="4"></fb:name><div>Hello</div>');
  7576. *
  7577. * @access private
  7578. * @param {DOMElement} dom DOM element
  7579. * @param {String} markup XFBML markup. It may contain reguarl
  7580. * HTML markup as well.
  7581. */
  7582. set: function(dom, markup, cb) {
  7583. FB.log('FB.XFBML.set() has been deprecated.');
  7584. dom.innerHTML = markup;
  7585. FB.XFBML.parse(dom, cb);
  7586. }
  7587. });
  7588. /**
  7589. * Copyright Facebook Inc.
  7590. *
  7591. * Licensed under the Apache License, Version 2.0 (the "License");
  7592. * you may not use this file except in compliance with the License.
  7593. * You may obtain a copy of the License at
  7594. *
  7595. * http://www.apache.org/licenses/LICENSE-2.0
  7596. *
  7597. * Unless required by applicable law or agreed to in writing, software
  7598. * distributed under the License is distributed on an "AS IS" BASIS,
  7599. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7600. * See the License for the specific language governing permissions and
  7601. * limitations under the License.
  7602. *
  7603. * @provides fb.waitable
  7604. * @layer data
  7605. * @requires fb.prelude fb.type fb.string fb.array fb.event fb.obj
  7606. */
  7607. /**
  7608. * A container for asynchronous data that may not be available immediately.
  7609. * This is base type for results returned from FB.Data.query()
  7610. * method.
  7611. *
  7612. * @class FB.Waitable
  7613. */
  7614. FB.subclass('Waitable', 'Obj',
  7615. /**
  7616. * Construct a Waitable object.
  7617. *
  7618. * @access private
  7619. * @constructor
  7620. */
  7621. function() {},
  7622. {
  7623. /**
  7624. * Set value property of the data object. This will
  7625. * cause "value" event to be fire on the object. Any callback functions
  7626. * that are waiting for the data through wait() methods will be invoked
  7627. * if the value was previously not set.
  7628. *
  7629. * @private
  7630. * @param {Object} value new value for the Waitable
  7631. */
  7632. set: function(value) {
  7633. this.setProperty('value', value);
  7634. },
  7635. /**
  7636. * Fire the error event.
  7637. *
  7638. * @access private
  7639. * @param ex {Exception} the exception object
  7640. */
  7641. error: function(ex) {
  7642. this.fire("error", ex);
  7643. },
  7644. /**
  7645. * Register a callback for an asynchronous value, which will be invoked when
  7646. * the value is ready.
  7647. *
  7648. * Example
  7649. * -------
  7650. *
  7651. * In this
  7652. * val v = get_a_waitable();
  7653. * v.wait(function (value) {
  7654. * // handle the value now
  7655. * },
  7656. * function(error) {
  7657. * // handle the errro
  7658. * });
  7659. * // later, whoever generated the waitable will call .set() and
  7660. * // invoke the callback
  7661. *
  7662. * @param {Function} callback A callback function that will be invoked
  7663. * when this.value is set. The value property will be passed to the
  7664. * callback function as a parameter
  7665. * @param {Function} errorHandler [optional] A callback function that
  7666. * will be invoked if there is an error in getting the value. The errorHandler
  7667. * takes an optional Error object.
  7668. */
  7669. wait: function(callback, errorHandler) {
  7670. // register error handler first incase the monitor call causes an exception
  7671. if (errorHandler) {
  7672. this.subscribe('error', errorHandler);
  7673. }
  7674. this.monitor('value', this.bind(function() {
  7675. if (this.value !== undefined) {
  7676. callback(this.value);
  7677. return true;
  7678. }
  7679. }));
  7680. }
  7681. });
  7682. /**
  7683. * Copyright Facebook Inc.
  7684. *
  7685. * Licensed under the Apache License, Version 2.0 (the "License");
  7686. * you may not use this file except in compliance with the License.
  7687. * You may obtain a copy of the License at
  7688. *
  7689. * http://www.apache.org/licenses/LICENSE-2.0
  7690. *
  7691. * Unless required by applicable law or agreed to in writing, software
  7692. * distributed under the License is distributed on an "AS IS" BASIS,
  7693. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7694. * See the License for the specific language governing permissions and
  7695. * limitations under the License.
  7696. *
  7697. * @provides fb.data.query
  7698. * @layer data
  7699. * @requires fb.waitable
  7700. */
  7701. /**
  7702. * Object that represents the results of an asynchronous FQL query, typically
  7703. * constructed by a call [FB.Data.query](FB.Data.query)().
  7704. *
  7705. * These objects can be used in one of two ways:
  7706. *
  7707. * * Call [wait](FB.Waitable.wait)() to handle the value when it's ready:
  7708. *
  7709. * var query = FB.Data.query(
  7710. * 'select name from page where username = 'barackobama');
  7711. * query.wait(function(result) {
  7712. * document.getElementById('page').innerHTML = result[0].name
  7713. * });
  7714. *
  7715. * * Pass it as an argument to a function that takes a Waitable. For example,
  7716. * in this case you can construct the second query without waiting for the
  7717. * results from the first, and it will combine them into one request:
  7718. *
  7719. * var query = FB.Data.query(
  7720. * 'select username from page where page_id = 6815841748');
  7721. * var dependentQuery = FB.Data.query(
  7722. * 'select name from page where username in ' +
  7723. * '(select username from {0})', query);
  7724. *
  7725. * // now wait for the results from the dependent query
  7726. * dependentQuery.wait(function(data) {
  7727. * document.getElementById('page').innerHTML = result[0].name
  7728. * });
  7729. *
  7730. * * Wait for multiple waitables at once with [FB.Data.waitOn](FB.Data.waitOn).
  7731. *
  7732. * Check out the [tests][tests] for more usage examples.
  7733. * [tests]: http://github.com/facebook/connect-js/blob/master/tests/js/data.js
  7734. *
  7735. * @class FB.Data.Query
  7736. * @access public
  7737. * @extends FB.Waitable
  7738. */
  7739. FB.subclass('Data.Query', 'Waitable',
  7740. function() {
  7741. if (!FB.Data.Query._c) {
  7742. FB.Data.Query._c = 1;
  7743. }
  7744. this.name = 'v_' + FB.Data.Query._c++;
  7745. },
  7746. {
  7747. /**
  7748. * Use the array of arguments using the FB.String.format syntax to build a
  7749. * query, parse it and populate this Query instance.
  7750. *
  7751. * @params args
  7752. */
  7753. parse: function(args) {
  7754. var
  7755. fql = FB.String.format.apply(null, args),
  7756. re = (/^select (.*?) from (\w+)\s+where (.*)$/i).exec(fql); // Parse it
  7757. this.fields = this._toFields(re[1]);
  7758. this.table = re[2];
  7759. this.where = this._parseWhere(re[3]);
  7760. for (var i=1; i < args.length; i++) {
  7761. if (FB.Type.isType(args[i], FB.Data.Query)) {
  7762. // Indicate this query can not be merged because
  7763. // others depend on it.
  7764. args[i].hasDependency = true;
  7765. }
  7766. }
  7767. return this;
  7768. },
  7769. /**
  7770. * Renders the query in FQL format.
  7771. *
  7772. * @return {String} FQL statement for this query
  7773. */
  7774. toFql: function() {
  7775. var s = 'select ' + this.fields.join(',') + ' from ' +
  7776. this.table + ' where ';
  7777. switch (this.where.type) {
  7778. case 'unknown':
  7779. s += this.where.value;
  7780. break;
  7781. case 'index':
  7782. s += this.where.key + '=' + this._encode(this.where.value);
  7783. break;
  7784. case 'in':
  7785. if (this.where.value.length == 1) {
  7786. s += this.where.key + '=' + this._encode(this.where.value[0]);
  7787. } else {
  7788. s += this.where.key + ' in (' +
  7789. FB.Array.map(this.where.value, this._encode).join(',') + ')';
  7790. }
  7791. break;
  7792. }
  7793. return s;
  7794. },
  7795. /**
  7796. * Encode a given value for use in a query string.
  7797. *
  7798. * @param value {Object} the value to encode
  7799. * @returns {String} the encoded value
  7800. */
  7801. _encode: function(value) {
  7802. return typeof(value) == 'string' ? FB.String.quote(value) : value;
  7803. },
  7804. /**
  7805. * Return the name for this query.
  7806. *
  7807. * TODO should this be renamed?
  7808. *
  7809. * @returns {String} the name
  7810. */
  7811. toString: function() {
  7812. return '#' + this.name;
  7813. },
  7814. /**
  7815. * Return an Array of field names extracted from a given string. The string
  7816. * here is a comma separated list of fields from a FQL query.
  7817. *
  7818. * Example:
  7819. * query._toFields('abc, def, ghi ,klm')
  7820. * Returns:
  7821. * ['abc', 'def', 'ghi', 'klm']
  7822. *
  7823. * @param s {String} the field selection string
  7824. * @returns {Array} the fields
  7825. */
  7826. _toFields: function(s) {
  7827. return FB.Array.map(s.split(','), FB.String.trim);
  7828. },
  7829. /**
  7830. * Parse the where clause from a FQL query.
  7831. *
  7832. * @param s {String} the where clause
  7833. * @returns {Object} parsed where clause
  7834. */
  7835. _parseWhere: function(s) {
  7836. // First check if the where is of pattern
  7837. // key = XYZ
  7838. var
  7839. re = (/^\s*(\w+)\s*=\s*(.*)\s*$/i).exec(s),
  7840. result,
  7841. value,
  7842. type = 'unknown';
  7843. if (re) {
  7844. // Now check if XYZ is either an number or string.
  7845. value = re[2];
  7846. // The RegEx expression for checking quoted string
  7847. // is from http://blog.stevenlevithan.com/archives/match-quoted-string
  7848. if (/^(["'])(?:\\?.)*?\1$/.test(value)) {
  7849. // Use eval to unquote the string
  7850. // convert
  7851. value = eval(value);
  7852. type = 'index';
  7853. } else if (/^\d+\.?\d*$/.test(value)) {
  7854. type = 'index';
  7855. }
  7856. }
  7857. if (type == 'index') {
  7858. // a simple <key>=<value> clause
  7859. result = { type: 'index', key: re[1], value: value };
  7860. } else {
  7861. // Not a simple <key>=<value> clause
  7862. result = { type: 'unknown', value: s };
  7863. }
  7864. return result;
  7865. }
  7866. });
  7867. /**
  7868. * Copyright Facebook Inc.
  7869. *
  7870. * Licensed under the Apache License, Version 2.0 (the "License");
  7871. * you may not use this file except in compliance with the License.
  7872. * You may obtain a copy of the License at
  7873. *
  7874. * http://www.apache.org/licenses/LICENSE-2.0
  7875. *
  7876. * Unless required by applicable law or agreed to in writing, software
  7877. * distributed under the License is distributed on an "AS IS" BASIS,
  7878. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  7879. * See the License for the specific language governing permissions and
  7880. * limitations under the License.
  7881. *
  7882. * @provides fb.data
  7883. * @layer data
  7884. * @requires fb.prelude
  7885. * fb.type
  7886. * fb.api
  7887. * fb.array
  7888. * fb.string
  7889. * fb.obj
  7890. * fb.data.query
  7891. * fb.json
  7892. */
  7893. /**
  7894. * Data access class for accessing Facebook data efficiently.
  7895. *
  7896. * FB.Data is a data layer that offers the following advantages over
  7897. * direct use of FB.Api:
  7898. *
  7899. * 1. Reduce number of individual HTTP requests through the following
  7900. * optimizations:
  7901. *
  7902. * a. Automatically combine individual data requests into a single
  7903. * multi-query request.
  7904. *
  7905. * b. Automatic query optimization.
  7906. *
  7907. * c. Enable caching of data through browser local cache (not implemented yet)
  7908. *
  7909. * 2. Reduce complexity of asynchronous API programming, especially multiple
  7910. * asynchronous request, though FB.Waitable and FB.waitOn.
  7911. *
  7912. * @class FB.Data
  7913. * @access public
  7914. * @static
  7915. */
  7916. FB.provide('Data', {
  7917. /**
  7918. * Performs a parameterized FQL query and returns a [FB.Data.query](FB.Data.query)
  7919. * object which can be waited on for the asynchronously fetched data.
  7920. *
  7921. * Examples
  7922. * --------
  7923. *
  7924. * Make a simple FQL call and handle the results.
  7925. *
  7926. * var query = FB.Data.query('select name, uid from user where uid={0}',
  7927. * user_id);
  7928. * query.wait(function(rows) {
  7929. * document.getElementById('name').innerHTML =
  7930. * 'Your name is ' + rows[0].name;
  7931. * });
  7932. *
  7933. * Display the names and events of 10 random friends. This can't be done
  7934. * using a simple FQL query because you need more than one field from more
  7935. * than one table, so we use FB.Data.query to help construct the call to
  7936. * [[api:fql.multiquery]].
  7937. *
  7938. * // First, get ten of the logged-in user's friends and the events they
  7939. * // are attending. In this query, the argument is just an int value
  7940. * // (the logged-in user id). Note, we are not firing the query yet.
  7941. * var query = FB.Data.query(
  7942. * "select uid, eid from event_member "
  7943. * + "where uid in "
  7944. * + "(select uid2 from friend where uid1 = {0}"
  7945. * + " order by rand() limit 10)",
  7946. * user_id);
  7947. *
  7948. * // Now, construct two dependent queries - one each to get the
  7949. * // names of the friends and the events referenced
  7950. * var friends = FB.Data.query(
  7951. * "select uid, name from user where uid in "
  7952. * + "(select uid from {0})", query);
  7953. * var events = FB.Data.query(
  7954. * "select eid, name from event where eid in "
  7955. * + " (select eid from {0})", query);
  7956. *
  7957. * // Now, register a callback which will execute once all three
  7958. * // queries return with data
  7959. * FB.Data.waitOn([query, friends, events], function() {
  7960. * // build a map of eid, uid to name
  7961. * var eventNames = friendNames = {};
  7962. * FB.Array.forEach(events.value, function(row) {
  7963. * eventNames[row.eid] = row.name;
  7964. * });
  7965. * FB.Array.forEach(friends.value, function(row) {
  7966. * friendNames[row.uid] = row.name;
  7967. * });
  7968. *
  7969. * // now display all the results
  7970. * var html = '';
  7971. * FB.Array.forEach(query.value, function(row) {
  7972. * html += '<p>'
  7973. * + friendNames[row.uid]
  7974. * + ' is attending '
  7975. * + eventNames[row.eid]
  7976. * + '</p>';
  7977. * });
  7978. * document.getElementById('display').innerHTML = html;
  7979. * });
  7980. *
  7981. * @param {String} template FQL query string template. It can contains
  7982. * optional formatted parameters in the format of '{<argument-index>}'.
  7983. * @param {Object} data optional 0-n arguments of data. The arguments can be
  7984. * either real data (String or Integer) or an [FB.Data.query](FB.Data.query)
  7985. * object from a previos [FB.Data.query](FB.Data.query).
  7986. * @return {FB.Data.Query}
  7987. * An async query object that contains query result.
  7988. */
  7989. query: function(template, data) {
  7990. var query = new FB.Data.Query().parse(arguments);
  7991. FB.Data.queue.push(query);
  7992. FB.Data._waitToProcess();
  7993. return query;
  7994. },
  7995. /**
  7996. * Wait until the results of all queries are ready. See also
  7997. * [FB.Data.query](FB.Data.query) for more examples of usage.
  7998. *
  7999. * Examples
  8000. * --------
  8001. *
  8002. * Wait for several queries to be ready, then perform some action:
  8003. *
  8004. * var queryTemplate = 'select name from profile where id={0}';
  8005. * var u1 = FB.Data.query(queryTemplate, 4);
  8006. * var u2 = FB.Data.query(queryTemplate, 1160);
  8007. * FB.Data.waitOn([u1, u2], function(args) {
  8008. * log('u1 value = '+ args[0].value);
  8009. * log('u2 value = '+ args[1].value);
  8010. * });
  8011. *
  8012. * Same as above, except we take advantage of JavaScript closures to
  8013. * avoid using args[0], args[1], etc:
  8014. *
  8015. * var queryTemplate = 'select name from profile where id={0}';
  8016. * var u1 = FB.Data.query(queryTemplate, 4);
  8017. * var u2 = FB.Data.query(queryTemplate, 1160);
  8018. * FB.Data.waitOn([u1, u2], function(args) {
  8019. * log('u1 value = '+ u1.value);
  8020. * log('u2 value = '+ u2.value);
  8021. * });
  8022. *
  8023. * Create a new Waitable that computes its value based on other Waitables:
  8024. *
  8025. * var friends = FB.Data.query('select uid2 from friend ' +
  8026. * 'where uid1=me()');
  8027. * // ...
  8028. * // Create a Waitable that is the count of friends
  8029. * var count = FB.Data.waitOn([friends], 'args[0].length');
  8030. * displayFriendsCount(count);
  8031. * // ...
  8032. * function displayFriendsCount(count) {
  8033. * count.wait(function(result) {
  8034. * log('friends count = ' + result);
  8035. * });
  8036. * }
  8037. *
  8038. * You can mix Waitables and data in the list of dependencies
  8039. * as well.
  8040. *
  8041. * var queryTemplate = 'select name from profile where id={0}';
  8042. * var u1 = FB.Data.query(queryTemplate, 4);
  8043. * var u2 = FB.Data.query(queryTemplate, 1160);
  8044. *
  8045. * FB.Data.waitOn([u1, u2, FB.getUserID()], function(args) {
  8046. * log('u1 = '+ args[0]);
  8047. * log('u2 = '+ args[1]);
  8048. * log('uid = '+ args[2]);
  8049. * });
  8050. *
  8051. * @param dependencies {Array} an array of dependencies to wait on. Each item
  8052. * could be a Waitable object or actual value.
  8053. * @param callback {Function} A function callback that will be invoked
  8054. * when all the data are ready. An array of ready data will be
  8055. * passed to the callback. If a string is passed, it will
  8056. * be evaluted as a JavaScript string.
  8057. * @return {FB.Waitable} A Waitable object that will be set with the return
  8058. * value of callback function.
  8059. */
  8060. waitOn: function(dependencies, callback) {
  8061. var
  8062. result = new FB.Waitable(),
  8063. count = dependencies.length;
  8064. // For developer convenience, we allow the callback
  8065. // to be a string of javascript expression
  8066. if (typeof(callback) == 'string') {
  8067. var s = callback;
  8068. callback = function(args) {
  8069. return eval(s);
  8070. };
  8071. }
  8072. FB.Array.forEach(dependencies, function(item) {
  8073. item.monitor('value', function() {
  8074. var done = false;
  8075. if (FB.Data._getValue(item) !== undefined) {
  8076. count--;
  8077. done = true;
  8078. }
  8079. if (count === 0) {
  8080. var value = callback(FB.Array.map(dependencies, FB.Data._getValue));
  8081. result.set(value !== undefined ? value : true);
  8082. }
  8083. return done;
  8084. });
  8085. });
  8086. return result;
  8087. },
  8088. /**
  8089. * Helper method to get value from Waitable or return self.
  8090. *
  8091. * @param item {FB.Waitable|Object} potential Waitable object
  8092. * @returns {Object} the value
  8093. */
  8094. _getValue: function(item) {
  8095. return FB.Type.isType(item, FB.Waitable) ? item.value : item;
  8096. },
  8097. /**
  8098. * Alternate method from query, this method is more specific but more
  8099. * efficient. We use it internally.
  8100. *
  8101. * @access private
  8102. * @param fields {Array} the array of fields to select
  8103. * @param table {String} the table name
  8104. * @param name {String} the key name
  8105. * @param value {Object} the key value
  8106. * @returns {FB.Data.Query} the query object
  8107. */
  8108. _selectByIndex: function(fields, table, name, value) {
  8109. var query = new FB.Data.Query();
  8110. query.fields = fields;
  8111. query.table = table;
  8112. query.where = { type: 'index', key: name, value: value };
  8113. FB.Data.queue.push(query);
  8114. FB.Data._waitToProcess();
  8115. return query;
  8116. },
  8117. /**
  8118. * Set up a short timer to ensure that we process all requests at once. If
  8119. * the timer is already set then ignore.
  8120. */
  8121. _waitToProcess: function() {
  8122. if (FB.Data.timer < 0) {
  8123. FB.Data.timer = setTimeout(FB.Data._process, 10);
  8124. }
  8125. },
  8126. /**
  8127. * Process the current queue.
  8128. */
  8129. _process: function() {
  8130. FB.Data.timer = -1;
  8131. var
  8132. mqueries = {},
  8133. q = FB.Data.queue;
  8134. FB.Data.queue = [];
  8135. for (var i=0; i < q.length; i++) {
  8136. var item = q[i];
  8137. if (item.where.type == 'index' && !item.hasDependency) {
  8138. FB.Data._mergeIndexQuery(item, mqueries);
  8139. } else {
  8140. mqueries[item.name] = item;
  8141. }
  8142. }
  8143. // Now make a single multi-query API call
  8144. var params = { q : {} };
  8145. FB.copy(params.q, mqueries, true, function(query) {
  8146. return query.toFql();
  8147. });
  8148. params.queries = FB.JSON.stringify(params.queries);
  8149. FB.api('/fql', 'GET', params,
  8150. function(result) {
  8151. if (result.error) {
  8152. FB.Array.forEach(mqueries, function(q1) {
  8153. q1.error(new Error(result.error.message));
  8154. });
  8155. } else {
  8156. FB.Array.forEach(result.data, function(o) {
  8157. mqueries[o.name].set(o.fql_result_set);
  8158. });
  8159. }
  8160. }
  8161. );
  8162. },
  8163. /**
  8164. * Check if y can be merged into x
  8165. * @private
  8166. */
  8167. _mergeIndexQuery: function(item, mqueries) {
  8168. var key = item.where.key,
  8169. value = item.where.value;
  8170. var name = 'index_' + item.table + '_' + key;
  8171. var master = mqueries[name];
  8172. if (!master) {
  8173. master = mqueries[name] = new FB.Data.Query();
  8174. master.fields = [key];
  8175. master.table = item.table;
  8176. master.where = {type: 'in', key: key, value: []};
  8177. }
  8178. // Merge fields
  8179. FB.Array.merge(master.fields, item.fields);
  8180. FB.Array.merge(master.where.value, [value]);
  8181. // Link data from master to item
  8182. master.wait(function(r) {
  8183. item.set(FB.Array.filter(r, function(x) {
  8184. return x[key] == value;
  8185. }));
  8186. });
  8187. },
  8188. timer: -1,
  8189. queue: []
  8190. });
  8191. /**
  8192. * Copyright Facebook Inc.
  8193. *
  8194. * Licensed under the Apache License, Version 2.0 (the "License");
  8195. * you may not use this file except in compliance with the License.
  8196. * You may obtain a copy of the License at
  8197. *
  8198. * http://www.apache.org/licenses/LICENSE-2.0
  8199. *
  8200. * Unless required by applicable law or agreed to in writing, software
  8201. * distributed under the License is distributed on an "AS IS" BASIS,
  8202. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8203. * See the License for the specific language governing permissions and
  8204. * limitations under the License.
  8205. *
  8206. *
  8207. *
  8208. * @provides fb.init.helper
  8209. * @requires fb.init
  8210. * fb.qs
  8211. * fb.array
  8212. */
  8213. // we do it in a setTimeout to wait until the current event loop as finished.
  8214. // this allows potential library code being included below this block (possible
  8215. // when being served from an automatically combined version)
  8216. window.setTimeout(function() {
  8217. // this is useful to enable fragment based initialization to reduce the
  8218. // amount of code needed to perform the common initialization logic
  8219. var pattern = /(connect.facebook.net|facebook.com\/assets.php).*?#(.*)/;
  8220. FB.Array.forEach(document.getElementsByTagName('script'), function(script) {
  8221. if (script.src) {
  8222. var match = pattern.exec(script.src);
  8223. if (match) {
  8224. var opts = FB.QS.decode(match[2]);
  8225. FB.Array.forEach(opts, function(val, key) {
  8226. if (val == '0') {
  8227. opts[key] = 0;
  8228. }
  8229. });
  8230. opts.oauth = true;
  8231. FB.init(opts);
  8232. }
  8233. }
  8234. });
  8235. // this is useful when the library is being loaded asynchronously
  8236. if (window.fbAsyncInit && !window.fbAsyncInit.hasRun) {
  8237. window.fbAsyncInit.hasRun = true;
  8238. fbAsyncInit();
  8239. }
  8240. }, 0);
  8241. /**
  8242. * Copyright Facebook Inc.
  8243. *
  8244. * Licensed under the Apache License, Version 2.0 (the "License");
  8245. * you may not use this file except in compliance with the License.
  8246. * You may obtain a copy of the License at
  8247. *
  8248. * http://www.apache.org/licenses/LICENSE-2.0
  8249. *
  8250. * Unless required by applicable law or agreed to in writing, software
  8251. * distributed under the License is distributed on an "AS IS" BASIS,
  8252. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8253. * See the License for the specific language governing permissions and
  8254. * limitations under the License.
  8255. *
  8256. * @provides fb.nativecalls
  8257. * @requires fb.prelude
  8258. * fb.ua
  8259. */
  8260. /**
  8261. * Provides a way to make native calls to a container Facebook iPhone or
  8262. * Android application from Javascript. Used by Mobile Canvas apps. Functions
  8263. * provided are:
  8264. *
  8265. * FB.Native.open(url) - takes a HTTP or HTTPS URL to be opened in the
  8266. * popup webview. Returns an object that implements a close() method closing
  8267. * the popup webview.
  8268. *
  8269. * FB.Native.postMessage(message, target) - sends the provided 'message' to
  8270. * the specified 'target' window, analagous to javascript's postMessage. Used
  8271. * for communication between two webviews (i.e. the canvas webview and a popup
  8272. * opened using FB.Native.open).
  8273. *
  8274. * FB.Native.setOrientation(orientation) (Deprecated - use the developer
  8275. * setting for orientation instead). Takes a string parameter that is either
  8276. * "portrait" or "landscape", and locks the screen in the specified orientation
  8277. *
  8278. * Since the native JS injection happens asynchronously, all native functions
  8279. * should be called within the callback to FB.Native.onready.
  8280. * Example:
  8281. * FB.Native.onready(function () {
  8282. * var popupWindow = FB.Native.open("http://facebook.com");
  8283. * popupWindow.close();
  8284. * });
  8285. *
  8286. */
  8287. FB.provide('Native', {
  8288. NATIVE_READY_EVENT: 'fbNativeReady',
  8289. /**
  8290. * Takes a callback function as a parameter and executes it once the native
  8291. * container has injected the javascript functions to make native calls.
  8292. *
  8293. * (This is necessary since native JS injection happens asynchronously)
  8294. */
  8295. onready: function(func) {
  8296. // Check that we're within a native container
  8297. if (!FB.UA.nativeApp()) {
  8298. FB.log('FB.Native.onready only works when the page is rendered ' +
  8299. 'in a WebView of the native Facebook app. Test if this is the ' +
  8300. 'case calling FB.UA.nativeApp()');
  8301. return;
  8302. }
  8303. // if the native container has injected JS but we haven't copied it
  8304. // into the FB.Native namespace, do that now. This way, all caller
  8305. // functions can use methods like FB.Native.open and not have to care
  8306. // about window.__fbNative
  8307. if (window.__fbNative && !this.nativeReady) {
  8308. FB.provide('Native', window.__fbNative);
  8309. }
  8310. // This will evaluate to true once the native app injects the JS methods
  8311. if (this.nativeReady) {
  8312. func();
  8313. } else {
  8314. // If the native interfaces haven't been injected yet,
  8315. // wait for an event to fire.
  8316. var nativeReadyCallback = function(evt) {
  8317. window.removeEventListener(FB.Native.NATIVE_READY_EVENT,
  8318. nativeReadyCallback);
  8319. FB.Native.onready(func);
  8320. };
  8321. window.addEventListener(FB.Native.NATIVE_READY_EVENT,
  8322. nativeReadyCallback,
  8323. false);
  8324. }
  8325. }
  8326. });
  8327. /**
  8328. * Copyright Facebook Inc.
  8329. *
  8330. * Licensed under the Apache License, Version 2.0 (the "License");
  8331. * you may not use this file except in compliance with the License.
  8332. * You may obtain a copy of the License at
  8333. *
  8334. * http://www.apache.org/licenses/LICENSE-2.0
  8335. *
  8336. * Unless required by applicable law or agreed to in writing, software
  8337. * distributed under the License is distributed on an "AS IS" BASIS,
  8338. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8339. * See the License for the specific language governing permissions and
  8340. * limitations under the License.
  8341. *
  8342. * @provides fb.pay
  8343. * @requires fb.prelude
  8344. * fb.arbiter
  8345. * fb.json
  8346. * fb.ui
  8347. * fb.xd
  8348. */
  8349. /**
  8350. * Implementation of the payment flow UI initiation
  8351. */
  8352. FB.provide('UIServer.Methods', {
  8353. 'pay.prompt': {
  8354. transform : function(call) {
  8355. var handler = FB.XD.handler(function(msg) {
  8356. call.cb(FB.JSON.parse(msg.response));
  8357. }, 'parent.frames[' + (window.name || 'iframe_canvas') + ']');
  8358. call.params.channel = handler;
  8359. FB.Arbiter.inform('Pay.Prompt', call.params);
  8360. return false;
  8361. }
  8362. }
  8363. });
  8364. FB.provide('UIServer.Methods', {
  8365. 'pay': {
  8366. size : { width: 555, height: 120 },
  8367. noHttps : true,
  8368. connectDisplay : 'popup',
  8369. transform : function(call) {
  8370. if (!FB._inCanvas) {
  8371. // Hack to keep backward compatibility
  8372. call.params.order_info = FB.JSON.stringify(call.params.order_info);
  8373. return call;
  8374. }
  8375. var handler = FB.XD.handler(function(msg) {
  8376. call.cb(FB.JSON.parse(msg.response));
  8377. }, 'parent.frames[' + (window.name || 'iframe_canvas') + ']');
  8378. call.params.channel = handler;
  8379. call.params.uiserver = true;
  8380. FB.Arbiter.inform('Pay.Prompt', call.params);
  8381. return false;
  8382. }
  8383. }
  8384. });
  8385. /**
  8386. * Copyright Facebook Inc.
  8387. *
  8388. * Licensed under the Apache License, Version 2.0 (the "License");
  8389. * you may not use this file except in compliance with the License.
  8390. * You may obtain a copy of the License at
  8391. *
  8392. * http://www.apache.org/licenses/LICENSE-2.0
  8393. *
  8394. * Unless required by applicable law or agreed to in writing, software
  8395. * distributed under the License is distributed on an "AS IS" BASIS,
  8396. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8397. * See the License for the specific language governing permissions and
  8398. * limitations under the License.
  8399. *
  8400. * @provides fb.helper
  8401. * @layer xfbml
  8402. * @requires fb.prelude
  8403. */
  8404. /**
  8405. * Helper class for XFBML
  8406. * @class FB.Helper
  8407. * @static
  8408. * @private
  8409. */
  8410. FB.provide('Helper', {
  8411. /**
  8412. * Check if an id is an user id, instead of a page id
  8413. *
  8414. * [NOTE:] This code is based on fbid_in_uid_range function in our server code
  8415. * If that function changes, we'd have to update this one as well.
  8416. *
  8417. * @param {uid} id
  8418. * @returns {Boolean} true if the given id is a user id
  8419. */
  8420. isUser: function(id) {
  8421. return id < 2200000000 ||
  8422. (id >= 100000000000000 && // 100T is first 64-bit UID
  8423. id <= 100099999989999) || // 100T + 3,333,333*30,000 - 1)
  8424. (id >= 89000000000000 && // DBTYPE_TEST2: see flib/core/fbid/hash.php
  8425. id <= 89999999999999);
  8426. },
  8427. /**
  8428. * Return the current user's UID if available.
  8429. *
  8430. * @returns {String|Number} returns the current user's UID or null
  8431. */
  8432. getLoggedInUser: function() {
  8433. return FB.getUserID(); // pass the buck to the auth response
  8434. },
  8435. /**
  8436. * Uppercase the first character of the String.
  8437. *
  8438. * @param s {String} the string
  8439. * @return {String} the string with an uppercase first character
  8440. */
  8441. upperCaseFirstChar: function(s) {
  8442. if (s.length > 0) {
  8443. return s.substr(0, 1).toUpperCase() + s.substr(1);
  8444. }
  8445. else {
  8446. return s;
  8447. }
  8448. },
  8449. /**
  8450. * Link to the explicit href or profile.php.
  8451. *
  8452. * @param userInfo {FB.UserInfo} User info object.
  8453. * @param html {String} Markup for the anchor tag.
  8454. * @param href {String} Custom href.
  8455. * @returns {String} the anchor tag markup
  8456. */
  8457. getProfileLink: function(userInfo, html, href) {
  8458. href = href || (userInfo ? FB.getDomain('www') + 'profile.php?id=' +
  8459. userInfo.uid : null);
  8460. if (href) {
  8461. html = '<a class="fb_link" href="' + href + '">' + html + '</a>';
  8462. }
  8463. return html;
  8464. },
  8465. /**
  8466. * Convenienve function to fire an event handler attribute value. This is a
  8467. * no-op for falsy values, eval for strings and invoke for functions.
  8468. *
  8469. * @param handler {Object}
  8470. * @param scope {Object}
  8471. * @param args {Array}
  8472. */
  8473. invokeHandler: function(handler, scope, args) {
  8474. if (handler) {
  8475. if (typeof handler === 'string') {
  8476. eval(handler);
  8477. } else if (handler.apply) {
  8478. handler.apply(scope, args || []);
  8479. }
  8480. }
  8481. },
  8482. /**
  8483. * Convenience function that fires the given event using both
  8484. * FB.Helper.fire and the prototype fire method. Passes the
  8485. * event raiser's href attriubute as an argument.
  8486. *
  8487. * @param evenName {String}
  8488. * @param eventRaiser {Object}
  8489. */
  8490. fireEvent: function(eventName, eventSource) {
  8491. var href = eventSource._attr.href;
  8492. eventSource.fire(eventName, href); // dynamically attached
  8493. FB.Event.fire(eventName, href, eventSource); // global
  8494. },
  8495. /**
  8496. * Converts a string to a function without using eval()
  8497. *
  8498. * From http://stackoverflow.com/questions/359788
  8499. */
  8500. executeFunctionByName: function(functionName /*, args */) {
  8501. var args = Array.prototype.slice.call(arguments, 1);
  8502. var namespaces = functionName.split(".");
  8503. var func = namespaces.pop();
  8504. var context = window;
  8505. for (var i = 0; i < namespaces.length; i++) {
  8506. context = context[namespaces[i]];
  8507. }
  8508. return context[func].apply(this, args);
  8509. }
  8510. });
  8511. /**
  8512. * Copyright Facebook Inc.
  8513. *
  8514. * Licensed under the Apache License, Version 2.0 (the "License");
  8515. * you may not use this file except in compliance with the License.
  8516. * You may obtain a copy of the License at
  8517. *
  8518. * http://www.apache.org/licenses/LICENSE-2.0
  8519. *
  8520. * Unless required by applicable law or agreed to in writing, software
  8521. * distributed under the License is distributed on an "AS IS" BASIS,
  8522. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8523. * See the License for the specific language governing permissions and
  8524. * limitations under the License.
  8525. *
  8526. *
  8527. *
  8528. * JavaScript library providing Facebook Connect integration.
  8529. *
  8530. * @provides fb.template_data
  8531. * @requires fb.api
  8532. * fb.json
  8533. * fb.helper
  8534. */
  8535. /**
  8536. * For preview templates, we need some per-app, per-user data that we fetch
  8537. * asynchronosly and cache in local browser storage for as long as possible.
  8538. * The events that might cause a local storage update are logging in or out
  8539. * and a periodical timeout.
  8540. *
  8541. * @class FB.TemplateData
  8542. * @static
  8543. * @access private
  8544. */
  8545. FB.provide('TemplateData', {
  8546. _initialized: false,
  8547. _version: 0,
  8548. _response: null,
  8549. _localStorageTimeout: 60 * 60 * 24,
  8550. // Set in ConnectStaticResponse as a temporary emergency
  8551. // tool until we're sure most Akamai-related issues are
  8552. // known to us.
  8553. _enabled: true,
  8554. enabled: function() {
  8555. return FB.TemplateData._enabled &&
  8556. FB.TemplateData._initialized &&
  8557. FB.TemplateData.supportsLocalStorage() &&
  8558. FB._userStatus == 'connected' &&
  8559. FB.TemplateData.getResponse();
  8560. },
  8561. supportsLocalStorage: function() {
  8562. try {
  8563. return 'localStorage' in window && window.localStorage !== null;
  8564. } catch (e) {
  8565. // Bug in old Firefox versions for disabled cookies
  8566. return false;
  8567. }
  8568. },
  8569. /**
  8570. * True if all of these are met:
  8571. * - there's a response in localStorage
  8572. * - it was set soon enough
  8573. * - the version is up to date
  8574. */
  8575. _isStale: function(response) {
  8576. if (!response || !response.version ||
  8577. response.version != FB.TemplateData._version ||
  8578. response.currentUserID != FB.getUserID()) {
  8579. return true;
  8580. }
  8581. var currentTime = Math.round((new Date()).getTime());
  8582. return (currentTime - response.setAt) / 1000.0 >
  8583. FB.TemplateData._localStorageTimeout;
  8584. },
  8585. getResponse: function() {
  8586. var self = FB.TemplateData;
  8587. try {
  8588. self._response = self._response ||
  8589. (self.supportsLocalStorage() &&
  8590. FB.JSON.parse(localStorage.FB_templateDataResponse || "null"));
  8591. } catch (e) {
  8592. // Catch possible bad data in localStorage
  8593. self._response = null;
  8594. }
  8595. if (self._isStale(self._response)) {
  8596. self.saveResponse(null);
  8597. }
  8598. return self._response;
  8599. },
  8600. saveResponse: function(response) {
  8601. FB.TemplateData._response = response;
  8602. if (FB.TemplateData.supportsLocalStorage()) {
  8603. localStorage.FB_templateDataResponse = FB.JSON.stringify(response);
  8604. }
  8605. },
  8606. /**
  8607. * Returns the data in FB_templateDataResponse or {}
  8608. * if one hasn't been loaded yet.
  8609. */
  8610. getData: function() {
  8611. var response = FB.TemplateData.getResponse();
  8612. return response ? response.data : {};
  8613. },
  8614. init: function(version) {
  8615. if (!version) {
  8616. return;
  8617. }
  8618. FB.TemplateData._initialized = true;
  8619. FB.TemplateData._version = version;
  8620. if (FB.TemplateData.supportsLocalStorage() &&
  8621. !('FB_templateDataResponse' in localStorage)) {
  8622. FB.TemplateData.clear();
  8623. }
  8624. },
  8625. clear: function() {
  8626. FB.TemplateData.saveResponse(null);
  8627. },
  8628. /**
  8629. * Called on auth.statusChange.
  8630. * Updates the state of this module as appropriate.
  8631. * Assumes init() has been called.
  8632. */
  8633. update: function(loginStatusResponse) {
  8634. if (FB._userStatus != 'connected') {
  8635. FB.TemplateData.clear();
  8636. }
  8637. if (FB._userStatus == 'connected' &&
  8638. !FB.TemplateData.getResponse()) {
  8639. FB.api({ method: 'dialog.template_data'}, function(response) {
  8640. if ('error_code' in response) {
  8641. // Something went wrong
  8642. return;
  8643. }
  8644. var data = {
  8645. data: response,
  8646. currentUserID: FB.getUserID(),
  8647. setAt: (new Date()).getTime(),
  8648. version: FB.TemplateData._version};
  8649. FB.TemplateData.saveResponse(data);
  8650. });
  8651. }
  8652. }
  8653. });
  8654. /**
  8655. * Copyright Facebook Inc.
  8656. *
  8657. * Licensed under the Apache License, Version 2.0 (the "License");
  8658. * you may not use this file except in compliance with the License.
  8659. * You may obtain a copy of the License at
  8660. *
  8661. * http://www.apache.org/licenses/LICENSE-2.0
  8662. *
  8663. * Unless required by applicable law or agreed to in writing, software
  8664. * distributed under the License is distributed on an "AS IS" BASIS,
  8665. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8666. * See the License for the specific language governing permissions and
  8667. * limitations under the License.
  8668. *
  8669. *
  8670. *
  8671. * @provides fb.template_ui
  8672. * @requires fb.type
  8673. * fb.obj
  8674. * fb.template_data
  8675. * fb.ua
  8676. * fb.ui
  8677. */
  8678. /**
  8679. * Provide Support for Template UI dialog
  8680. *
  8681. * @class FB.TemplateUI
  8682. * @access private
  8683. */
  8684. FB.subclass('TemplateUI', 'Obj',
  8685. // Constructor
  8686. function(method, isPreload) {
  8687. this.method = method;
  8688. var in_iframe = FB.UA.nativeApp() ? 0 : 1;
  8689. var query_params =
  8690. {display: 'touch',
  8691. preview_template: 1,
  8692. in_iframe: in_iframe,
  8693. locale: FB._locale,
  8694. v: FB.TemplateUI._version,
  8695. user_agent: navigator.userAgent
  8696. };
  8697. if (window.devicePixelRatio) {
  8698. query_params.m_pixel_ratio = window.devicePixelRatio;
  8699. }
  8700. var query_string = FB.QS.encode(query_params);
  8701. // Create a dialog that points to akamai cached template
  8702. // ui dialog, then hide the dialog, so that it may be used
  8703. // later.
  8704. this.cachedCall = {
  8705. url: FB.getDomain('staticfb') + 'dialog/' + method + '?' + query_string,
  8706. frameName: FB.guid(),
  8707. id: FB.guid(),
  8708. size: FB.UIServer.getDefaultSize(),
  8709. hideLoader: true
  8710. };
  8711. // A Mobile Safari bug prevents pages from caching even if only fragment in
  8712. // a url changes, so we use cross-domain communication to pass data
  8713. // parameters. This also means the parameter size won't be limited browser's
  8714. // maximum url length.
  8715. FB.XD.handler(this.bind(function(data) {
  8716. if (data.type == 'getParams') {
  8717. // store returnCb for later when the cached dialog is
  8718. // used.
  8719. this.setProperty('getParamsCb', data.returnCb);
  8720. }
  8721. }), 'parent', true, this.cachedCall.frameName);
  8722. // Create an iframe first, then hide the dialog. This allows us to effectively
  8723. // hide the time it takes for a browser to parse and render a page.
  8724. // On iPhone 3GS, that can be 500ms.
  8725. // Note currently we cannot pre-load for popup and native dialogs because
  8726. // we can't create them in hidden mode.
  8727. if (in_iframe) {
  8728. FB.UIServer.iframe(this.cachedCall);
  8729. FB.Dialog.hide(this.cachedCall.root);
  8730. } else if (isPreload && !FB.TemplateUI._preloads[this.cachedCall.url]) {
  8731. // For now, we don't have a good way to preload template dialog inside
  8732. // native app because we don't create a hidden dialog and show it
  8733. // later.
  8734. // However, if we create a hidden iframe to the same url, we'd at least
  8735. // be able to pre-fetch resources from Akamai in case of cold cache since
  8736. // template dialog is completedly static and cacheable.
  8737. var container = document.createElement('div');
  8738. FB.TemplateUI._preloads[this.cachedCall.url] = {container: container};
  8739. FB.Content.insertIframe({
  8740. url: this.cachedCall.url,
  8741. root: FB.Content.appendHidden(container)
  8742. });
  8743. }
  8744. },
  8745. // Instance Methods
  8746. {
  8747. /**
  8748. * Use the template UI
  8749. * @access private
  8750. * @param call {Object} call parameters
  8751. */
  8752. use: function(call) {
  8753. if (!this.cachedCall.root) {
  8754. FB.UIServer.touch(this.cachedCall);
  8755. // Check if there is an iframe that was used to preload
  8756. // the same url. If so, remove it because we don't need it
  8757. // anymore
  8758. var preload = FB.TemplateUI._preloads[this.cachedCall.url];
  8759. if (preload && preload.container) {
  8760. preload.container.parentNode.removeChild(preload.container);
  8761. delete preload.container;
  8762. }
  8763. }
  8764. call.ui_created = true;
  8765. // Set dialog root to that of the cached one.
  8766. call.root = this.cachedCall.root;
  8767. // Switch any place where cached call id is used.
  8768. // Absolutely terrible. Needs refactoring.
  8769. // FB.UIServer._loadedNodes will keep the new id, which
  8770. // is not related to the DOM in any way.
  8771. FB.UIServer.setLoadedNode(call,
  8772. FB.UIServer.getLoadedNode(this.cachedCall.id));
  8773. delete FB.UIServer._loadedNodes[this.cachedCall.id];
  8774. // FB.Dialog._dialogs and FB.Dialog._loadedNodes[frame].fbCallID
  8775. // will keep the real iframe's id
  8776. // because that's used to later resize the iframes.
  8777. var dialog = FB.Dialog._dialogs[call.id];
  8778. FB.Dialog._dialogs[this.cachedCall.id] = dialog;
  8779. dialog.id = this.cachedCall.id;
  8780. delete FB.Dialog._dialogs[call.id];
  8781. FB.UIServer.getLoadedNode(call).fbCallID = this.cachedCall.id;
  8782. this.cachedCall.id = call.id;
  8783. var template_params = {};
  8784. FB.copy(template_params, call.params);
  8785. FB.copy(template_params, FB.TemplateData.getData()[this.method]);
  8786. template_params.frictionless =
  8787. FB.TemplateUI.isFrictionlessAppRequest(this.method, template_params);
  8788. template_params.common = FB.TemplateData.getData().common;
  8789. template_params.method = this.method;
  8790. this.setParams(template_params);
  8791. // Note that the same check in FB.UIServer.touch covers the regular version
  8792. // of the dialog, and this one covers the template version. This is because
  8793. // the default cb is not associated with the template dialog until
  8794. // FB.TemplateUI.use() is called.
  8795. if (FB.UA.nativeApp()) {
  8796. FB.UIServer._popupMonitor();
  8797. }
  8798. },
  8799. /**
  8800. * Use postMessage to pass data parameter to template iframe
  8801. * @access private
  8802. * @param params {Object} data parametes
  8803. */
  8804. setParams: function(params) {
  8805. // We need to wait until the iframe send callback cb
  8806. // for getParams
  8807. this.monitor('getParamsCb', this.bind(function() {
  8808. if (this.getParamsCb) {
  8809. var dialogWindow = frames[this.cachedCall.frameName] ||
  8810. FB.UIServer.getLoadedNode(this.cachedCall);
  8811. dialogWindow.postMessage(FB.JSON.stringify(
  8812. {params: params,
  8813. cb: this.getParamsCb
  8814. }), '*');
  8815. return true;
  8816. }
  8817. }));
  8818. }
  8819. });
  8820. // Static methods
  8821. FB.provide('TemplateUI', {
  8822. _timer: null,
  8823. _cache: {},
  8824. _preloads: {},
  8825. // Overridden by the PLATFORM_DIALOG_TEMPLATE_VERSION sitevar.
  8826. // A value of 0 disables templates.
  8827. _version: 0,
  8828. /**
  8829. * Initialization function.
  8830. */
  8831. init: function() {
  8832. FB.TemplateData.init(FB.TemplateUI._version);
  8833. FB.TemplateUI.initCache();
  8834. },
  8835. /**
  8836. * Use cached UI for dialog
  8837. * @param method {string} method name
  8838. * @param call {call} call parameters
  8839. */
  8840. useCachedUI: function(method, call) {
  8841. try {
  8842. // Ensure the relevant iframe is rendered. Usually a no-op.
  8843. FB.TemplateUI.populateCache();
  8844. cache = FB.TemplateUI._cache[method];
  8845. // Ensure we don't try to reuse the same iframe later
  8846. delete FB.TemplateUI._cache[method];
  8847. cache.use(call);
  8848. } catch (e) {
  8849. // To prevent an eternally broken state
  8850. // caused by completely unexpected data-related errors.
  8851. FB.TemplateData.clear();
  8852. }
  8853. },
  8854. /**
  8855. * Will prerender any iframes not already in
  8856. * FB.TemplateUI._cache. Called at init time and after
  8857. * a cached iframe is closed.
  8858. */
  8859. populateCache: function(isPreload) {
  8860. if (!FB.TemplateData.enabled() || !FB.UA.mobile()) {
  8861. return;
  8862. }
  8863. clearInterval(FB.TemplateUI._timer);
  8864. var methods = {feed: true, apprequests: true};
  8865. for (var method in methods) {
  8866. if (!(method in FB.TemplateUI._cache)) {
  8867. FB.TemplateUI._cache[method] = new FB.TemplateUI(method, isPreload);
  8868. }
  8869. }
  8870. },
  8871. /**
  8872. * We use a timer to check and initialize cached UI for two
  8873. * reasons:
  8874. * 1. Try to delay the loading of the cached UI to minimize impact
  8875. * on application
  8876. * 2. If the template data is not ready, we need to wait for it.
  8877. */
  8878. initCache: function() {
  8879. FB.TemplateUI._timer = setInterval(function() {
  8880. FB.TemplateUI.populateCache(true);
  8881. }, 2000);
  8882. },
  8883. /**
  8884. * Only the feed and apprequests dialogs run template versions
  8885. * when possible, though some of their features can't be or aren't
  8886. * implemented with templates yet.
  8887. */
  8888. supportsTemplate: function(method, call) {
  8889. return FB.TemplateData.enabled() &&
  8890. FB.TemplateUI.paramsAllowTemplate(method, call.params) &&
  8891. call.params.display === 'touch' &&
  8892. FB.UA.mobile();
  8893. },
  8894. /**
  8895. * Feed templates don't support these:
  8896. * - dialogs posting to a friend's wall
  8897. * - deprecated attachment params
  8898. * - source, which means video
  8899. * App Request templates don't support these:
  8900. * - pre-specified friend
  8901. * - suggestions
  8902. * @param method {String} method name
  8903. * @param app_params {Object} the FB.ui parameters
  8904. * @return {Boolean} whether the call can use a template dialog
  8905. */
  8906. paramsAllowTemplate: function(method, app_params) {
  8907. var bad_params =
  8908. {feed: {to: 1, attachment: 1, source: 1},
  8909. apprequests: {}};
  8910. if (!(method in bad_params)) {
  8911. return false;
  8912. }
  8913. for (var param in bad_params[method]) {
  8914. if (app_params[param]) {
  8915. return false;
  8916. }
  8917. }
  8918. return !FB.TemplateUI.willWriteOnGet(method, app_params);
  8919. },
  8920. isFrictionlessAppRequest: function(method, app_params) {
  8921. return method === 'apprequests' && FB.Frictionless &&
  8922. FB.Frictionless._useFrictionless;
  8923. },
  8924. /**
  8925. * Frictionless requests kick off the full-param version
  8926. * of the dialog if enabled for the specified recipient
  8927. * because they send out the notification on the first
  8928. * server request.
  8929. * @param method {String} method name
  8930. * @param app_params {Object} the FB.ui parameters
  8931. * @return {Boolean} true if a regular call would
  8932. * write to the DB on the first request
  8933. */
  8934. willWriteOnGet: function(method, app_params) {
  8935. return FB.TemplateUI.isFrictionlessAppRequest(method, app_params) &&
  8936. app_params.to &&
  8937. FB.Frictionless.isAllowed(app_params.to);
  8938. }
  8939. });
  8940. /**
  8941. * Copyright Facebook Inc.
  8942. *
  8943. * Licensed under the Apache License, Version 2.0 (the "License");
  8944. * you may not use this file except in compliance with the License.
  8945. * You may obtain a copy of the License at
  8946. *
  8947. * http://www.apache.org/licenses/LICENSE-2.0
  8948. *
  8949. * Unless required by applicable law or agreed to in writing, software
  8950. * distributed under the License is distributed on an "AS IS" BASIS,
  8951. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8952. * See the License for the specific language governing permissions and
  8953. * limitations under the License.
  8954. *
  8955. *
  8956. * @provides fb.uri
  8957. * @requires fb.ua
  8958. */
  8959. /**
  8960. * URI Handling
  8961. */
  8962. FB.provide('URI', {
  8963. /**
  8964. * Resolve a relative URL to an absolute URL. An absolute URL will resolve to
  8965. * itself. The empty string resolves to the current window location.
  8966. */
  8967. resolve: function(uri) {
  8968. if (!uri) { // IE handles this case poorly, so we will be explicit about it
  8969. return window.location.href;
  8970. }
  8971. var div = document.createElement('div');
  8972. // This uses `innerHTML` because anything else doesn't resolve properly or
  8973. // causes an HTTP request in IE6/7.
  8974. div.innerHTML = '<a href="' + uri.replace(/"/g, '&quot;') + '"></a>';
  8975. return div.firstChild.href; // This will be an absolute URL. MAGIC!
  8976. }
  8977. });
  8978. /**
  8979. * Copyright Facebook Inc.
  8980. *
  8981. * Licensed under the Apache License, Version 2.0 (the "License");
  8982. * you may not use this file except in compliance with the License.
  8983. * You may obtain a copy of the License at
  8984. *
  8985. * http://www.apache.org/licenses/LICENSE-2.0
  8986. *
  8987. * Unless required by applicable law or agreed to in writing, software
  8988. * distributed under the License is distributed on an "AS IS" BASIS,
  8989. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  8990. * See the License for the specific language governing permissions and
  8991. * limitations under the License.
  8992. *
  8993. * @provides fb.xfbml.element
  8994. * @layer xfbml
  8995. * @requires fb.type fb.event fb.array
  8996. */
  8997. /**
  8998. * Base class for all XFBML elements. To create your own XFBML element, make a
  8999. * class that derives from this, and then call [FB.XFBML.registerTag](FB.XFBML.registerTag).
  9000. *
  9001. * @access private
  9002. * @class FB.XFBML.Element
  9003. */
  9004. FB.Class('XFBML.Element',
  9005. /**
  9006. * Create a new Element.
  9007. *
  9008. * @access private
  9009. * @constructor
  9010. * @param dom {DOMElement} the DOMElement for the tag
  9011. */
  9012. function(dom) {
  9013. this.dom = dom;
  9014. },
  9015. FB.copy({
  9016. /**
  9017. * Get the value of an attribute associated with this tag.
  9018. *
  9019. * Note, the transform function is never executed over the default value. It
  9020. * is only used to transform user set attribute values.
  9021. *
  9022. * @access private
  9023. * @param name {String} Name of the attribute.
  9024. * @param defaultValue {Object} Default value if attribute isn't set.
  9025. * @param transform {Function} Optional function to transform found value.
  9026. * @return {Object} final value
  9027. */
  9028. getAttribute: function(name, defaultValue, transform) {
  9029. var value = FB.XFBML.getAttr(this.dom, name);
  9030. return value ? (transform ? transform(value) : value) : defaultValue;
  9031. },
  9032. /**
  9033. * Helper function to extract boolean attribute value.
  9034. *
  9035. * @access private
  9036. * @param name {String} Name of the attribute.
  9037. * @param defaultValue {Object} Default value if attribute isn't set.
  9038. */
  9039. _getBoolAttribute: function(name, defaultValue) {
  9040. if (FB.XFBML.getAttr(this.dom, name) === null) {
  9041. return defaultValue;
  9042. }
  9043. return FB.XFBML.getBoolAttr(this.dom, name);
  9044. },
  9045. /**
  9046. * Get an integer value for size in pixels.
  9047. *
  9048. * @access private
  9049. * @param name {String} Name of the attribute.
  9050. * @param defaultValue {Object} Default value if attribute isn't set.
  9051. */
  9052. _getPxAttribute: function(name, defaultValue) {
  9053. return this.getAttribute(name, defaultValue, function(s) {
  9054. var size = parseInt(s.replace('px', ''), 10);
  9055. if (isNaN(size)) {
  9056. return defaultValue;
  9057. } else {
  9058. return size;
  9059. }
  9060. });
  9061. },
  9062. /**
  9063. * Get a value if it is in the allowed list, otherwise return the default
  9064. * value. This function ignores case and expects you to use only lower case
  9065. * allowed values.
  9066. *
  9067. * @access private
  9068. * @param name {String} Name of the attribute.
  9069. * @param defaultValue {Object} Default value
  9070. * @param allowed {Array} List of allowed values.
  9071. */
  9072. _getAttributeFromList: function(name, defaultValue, allowed) {
  9073. return this.getAttribute(name, defaultValue, function(s) {
  9074. s = s.toLowerCase();
  9075. if (FB.Array.indexOf(allowed, s) > -1) {
  9076. return s;
  9077. } else {
  9078. return defaultValue;
  9079. }
  9080. });
  9081. },
  9082. /**
  9083. * Check if this node is still valid and in the document.
  9084. *
  9085. * @access private
  9086. * @returns {Boolean} true if element is valid
  9087. */
  9088. isValid: function() {
  9089. for (var dom = this.dom; dom; dom = dom.parentNode) {
  9090. if (dom == document.body) {
  9091. return true;
  9092. }
  9093. }
  9094. },
  9095. /**
  9096. * Clear this element and remove all contained elements.
  9097. *
  9098. * @access private
  9099. */
  9100. clear: function() {
  9101. this.dom.innerHTML = '';
  9102. }
  9103. }, FB.EventProvider));
  9104. /**
  9105. * @provides fb.xfbml.iframewidget
  9106. * @requires fb.arbiter
  9107. * fb.content
  9108. * fb.event
  9109. * fb.qs
  9110. * fb.type
  9111. * fb.xfbml.element
  9112. * @css fb.css.iframewidget
  9113. * @layer xfbml
  9114. */
  9115. /**
  9116. * Copyright Facebook Inc.
  9117. *
  9118. * Licensed under the Apache License, Version 2.0 (the "License");
  9119. * you may not use this file except in compliance with the License.
  9120. * You may obtain a copy of the License at
  9121. *
  9122. * http://www.apache.org/licenses/LICENSE-2.0
  9123. *
  9124. * Unless required by applicable law or agreed to in writing, software
  9125. * distributed under the License is distributed on an "AS IS" BASIS,
  9126. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9127. * See the License for the specific language governing permissions and
  9128. * limitations under the License.
  9129. */
  9130. /**
  9131. * Base implementation for iframe based XFBML Widgets.
  9132. *
  9133. * @class FB.XFBML.IframeWidget
  9134. * @extends FB.XFBML.Element
  9135. * @private
  9136. */
  9137. FB.subclass('XFBML.IframeWidget', 'XFBML.Element', null, {
  9138. /**
  9139. * The name that should be used for the 'name' attribute of
  9140. * the iframe. Normally null, which means that it can be auto-generated
  9141. * without any regard for convention, but it can be set by the
  9142. * subclass if the name is important.
  9143. */
  9144. _iframeName: null,
  9145. /**
  9146. * Indicate if the loading animation should be shown while the iframe is
  9147. * loading.
  9148. */
  9149. _showLoader: true,
  9150. /**
  9151. * Indicate if the widget should be reprocessed when the user enters or
  9152. * leaves the "unknown" state. (Logs in/out of facebook, but not the
  9153. * application.)
  9154. */
  9155. _refreshOnAuthChange: false,
  9156. /**
  9157. * Indicates if the widget should be reprocessed on auth.statusChange events.
  9158. * This is the default for XFBML Elements, but is usually undesirable for
  9159. * Iframe Widgets.
  9160. */
  9161. _allowReProcess: false,
  9162. /**
  9163. * Indicates if the widget should be loaded from a static response cached
  9164. * version the CDN, only needed for high performance widgets that need to
  9165. * minimize TTI.
  9166. */
  9167. _fetchPreCachedLoader: false,
  9168. /**
  9169. * Indicates when the widget will be made visible.
  9170. *
  9171. * load: when the iframe's page onload event is fired
  9172. * resize: when the first resize message is received
  9173. * immediate: when there is any HTML.
  9174. */
  9175. _visibleAfter: 'load',
  9176. /**
  9177. * Indicates whether or not the widget should be rendered using
  9178. * WidgetPipe. The default is false, but this same field should
  9179. * be set to true if the IframeWidget subclass has WidgetPipe support.
  9180. */
  9181. _widgetPipeEnabled: false,
  9182. /////////////////////////////////////////////////////////////////////////////
  9183. // Methods the implementation MUST override
  9184. /////////////////////////////////////////////////////////////////////////////
  9185. /**
  9186. * Implemented by the inheriting class to return a **name** and **params**.
  9187. *
  9188. * The name is the the file name in the plugins directory. So the name "fan"
  9189. * translates to the path "/plugins/fan.php". This enforces consistency.
  9190. *
  9191. * The params should be the query params needed for the widget. API Key,
  9192. * Session Key, SDK and Locale are automatically included.
  9193. *
  9194. * @return {Object} an object containing a **name** and **params**.
  9195. */
  9196. getUrlBits: function() {
  9197. throw new Error('Inheriting class needs to implement getUrlBits().');
  9198. },
  9199. /////////////////////////////////////////////////////////////////////////////
  9200. // Methods the implementation CAN override
  9201. /////////////////////////////////////////////////////////////////////////////
  9202. /**
  9203. * This method is invoked before any processing is done to do any initial
  9204. * setup and do any necessary validation on the attributes. A return value of
  9205. * false will indicate that validation was unsuccessful and processing will
  9206. * be halted. If you are going to return false and halt processing, you
  9207. * should ensure you use FB.log() to output a short informative message
  9208. * before doing so.
  9209. *
  9210. * @return {Boolean} true to continue processing, false to halt it
  9211. */
  9212. setupAndValidate: function() {
  9213. return true;
  9214. },
  9215. /**
  9216. * This is useful for setting up event handlers and such which should not be
  9217. * run again if the widget is reprocessed.
  9218. */
  9219. oneTimeSetup: function() {},
  9220. /**
  9221. * Implemented by the inheriting class to return the initial size for the
  9222. * iframe. If the inheriting class does not implement this, we default to
  9223. * null which implies no element level style. This is useful if you are
  9224. * defining the size based on the className.
  9225. *
  9226. * @return {Object} object with a width and height as Numbers (pixels assumed)
  9227. */
  9228. getSize: function() {},
  9229. /**
  9230. * Generates and returns a unique frame name for this particular
  9231. * iframe if a name hasn't already been provided *and* the
  9232. * widget/plugin is widget-pipe-enabled, so that one needs to
  9233. * be generated. If the name has already been generated, or
  9234. * if it hasn't but we don't care to generate one because it
  9235. * isn't widget-pipe enabled, then we just return what the
  9236. * iframe name currently is.
  9237. *
  9238. * @return {String} the name given to the iframe.
  9239. */
  9240. getIframeName: function() {
  9241. if (!this._iframeName &&
  9242. this._widgetPipeEnabled &&
  9243. FB.XFBML.shouldUseWidgetPipe()) {
  9244. this._iframeName = this.generateWidgetPipeIframeName();
  9245. FB.XFBML.IframeWidget.allWidgetPipeIframes[this._iframeName] = this;
  9246. if (FB.XFBML.IframeWidget.masterWidgetPipeIframe === null) {
  9247. FB.XFBML.IframeWidget.masterWidgetPipeIframe = this;
  9248. }
  9249. }
  9250. return this._iframeName;
  9251. },
  9252. /**
  9253. * Implemented by a subclass that wants to attach a title as an attribute
  9254. * to the top-level iframe tag.
  9255. */
  9256. getIframeTitle: function() {},
  9257. /////////////////////////////////////////////////////////////////////////////
  9258. // Public methods the implementation CAN use
  9259. /////////////////////////////////////////////////////////////////////////////
  9260. /**
  9261. * Get a channel url for use with this widget.
  9262. *
  9263. * @return {String} the channel URL
  9264. */
  9265. getChannelUrl: function() {
  9266. if (!this._channelUrl) {
  9267. // parent.parent => the message will be going from cdn => fb => app (with
  9268. // cdn being the deepest frame, and app being the top frame)
  9269. var self = this;
  9270. this._channelUrl = FB.XD.handler(function(message) {
  9271. self.fire('xd.' + message.type, message);
  9272. }, 'parent.parent', true);
  9273. }
  9274. return this._channelUrl;
  9275. },
  9276. /**
  9277. * Returns the iframe node (if it has already been created).
  9278. *
  9279. * @return {DOMElement} the iframe DOM element
  9280. */
  9281. getIframeNode: function() {
  9282. // not caching to allow for the node to change over time without needing
  9283. // house-keeping for the cached reference.
  9284. return this.dom.getElementsByTagName('iframe')[0];
  9285. },
  9286. /**
  9287. * Call FB.Arbiter.inform with values appropriate to a social plugin embedded
  9288. * in a 3rd party site. The `this.loaded` variable is changed in an
  9289. * `iframe.onload` callback, so it's state is not guaranteed to be correct in
  9290. * the context of an `iframe.onload` event handler. Therefore, this function
  9291. * will not necessarily work if called from inside an `iframe.onload` event
  9292. * handler.
  9293. */
  9294. arbiterInform: function(event, message, behavior) {
  9295. if (this.loaded) {
  9296. this._arbiterInform(event, message, behavior);
  9297. } else {
  9298. this.subscribe( // inform once iframe exists
  9299. 'iframe.onload',
  9300. FB.bind(this._arbiterInform, this, event, message, behavior));
  9301. }
  9302. },
  9303. /**
  9304. * This is an internal helper to deal with synchronization to prevent race
  9305. * conditions in arbiterInform. Clients should use arbiterInform.
  9306. */
  9307. _arbiterInform: function(event, message, behavior) {
  9308. var relation = 'parent.frames["' + this.getIframeNode().name + '"]';
  9309. FB.Arbiter.inform(
  9310. event, message, relation, window.location.protocol == 'https:', behavior);
  9311. },
  9312. /**
  9313. * Returns the default domain that should be used for all
  9314. * plugins served from our web tier.
  9315. */
  9316. getDefaultWebDomain: function() {
  9317. return 'www';
  9318. },
  9319. /**
  9320. * Returns the default domain that should be used for all
  9321. * plugins served from our Akamai tier.
  9322. */
  9323. getDefaultStaticDomain: function() {
  9324. return 'cdn';
  9325. },
  9326. /////////////////////////////////////////////////////////////////////////////
  9327. // Private methods the implementation MUST NOT use or override
  9328. /////////////////////////////////////////////////////////////////////////////
  9329. /**
  9330. * Inheriting classes should not touch the DOM directly, and are only allowed
  9331. * to override the methods defined at the top.
  9332. *
  9333. * @param force {Boolean} force reprocessing of the node
  9334. */
  9335. process: function(force) {
  9336. // guard agains reprocessing if needed
  9337. if (this._done) {
  9338. if (!this._allowReProcess && !force) {
  9339. return;
  9340. }
  9341. this.clear();
  9342. } else {
  9343. this._oneTimeSetup();
  9344. }
  9345. this._done = true;
  9346. if (!this.setupAndValidate()) {
  9347. // failure to validate means we're done rendering what we can
  9348. this.fire('render');
  9349. return;
  9350. }
  9351. // show the loader if needed
  9352. if (this._showLoader) {
  9353. this._addLoader();
  9354. }
  9355. // it's always hidden by default
  9356. FB.Dom.addCss(this.dom, 'fb_iframe_widget');
  9357. if (this._visibleAfter != 'immediate') {
  9358. FB.Dom.addCss(this.dom, 'fb_hide_iframes');
  9359. } else {
  9360. this.subscribe('iframe.onload', FB.bind(this.fire, this, 'render'));
  9361. }
  9362. // the initial size
  9363. var size = this.getSize() || {};
  9364. var url = this.getFullyQualifiedURL();
  9365. if (size.width == '100%') {
  9366. FB.Dom.addCss(this.dom, 'fb_iframe_widget_fluid');
  9367. }
  9368. FB.Content.insertIframe({
  9369. url : url,
  9370. root : this.dom.appendChild(document.createElement('span')),
  9371. name : this.getIframeName(),
  9372. title : this.getIframeTitle(),
  9373. className : FB._localeIsRtl ? 'fb_rtl' : 'fb_ltr',
  9374. height : size.height,
  9375. width : size.width,
  9376. onload : FB.bind(this.fire, this, 'iframe.onload')
  9377. });
  9378. this.loaded = false;
  9379. this.subscribe(
  9380. 'iframe.onload', FB.bind(function() { this.loaded = true; }, this));
  9381. },
  9382. /**
  9383. * The default way we generate iframe names. This (protected)
  9384. * method should be overridden by the subclass if it requires
  9385. * a different iframe naming convention.
  9386. *
  9387. * @return {String} the name that should be given to the widget-pipe
  9388. * enabled iframe.
  9389. */
  9390. generateWidgetPipeIframeName: function() {
  9391. FB.XFBML.IframeWidget.widgetPipeIframeCount++;
  9392. return 'fb_iframe_' + FB.XFBML.IframeWidget.widgetPipeIframeCount;
  9393. },
  9394. /**
  9395. * Computes the full URL that should be implanted iframe src.
  9396. * In the case of short URLs, the traditional GET approach is used,
  9397. * but for excessively large URLs we set the URL to be 'about:blank'
  9398. * and use POST to get the content after the iframe loads (note the
  9399. * subscription to and eventually unsubscription from the iframe.onload
  9400. * event. Clever.)
  9401. *
  9402. * @return {String} the fully qualified, properly encoded URL
  9403. */
  9404. getFullyQualifiedURL: function() {
  9405. if (FB.XFBML.shouldUseWidgetPipe() && this._widgetPipeEnabled) {
  9406. return this._getWidgetPipeShell();
  9407. }
  9408. // we use a GET request if the URL is less than 2k, otherwise we need to do
  9409. // a <form> POST. we prefer a GET because it prevents the "POST resend"
  9410. // warning browsers shown on page refresh.
  9411. var url = this._getURL();
  9412. if (!this._fetchPreCachedLoader) {
  9413. url += '?' + FB.QS.encode(this._getQS());
  9414. }
  9415. if (url.length > 2000) {
  9416. // we will POST the form once the empty about:blank iframe is done loading
  9417. url = 'about:blank';
  9418. var onload = FB.bind(function() {
  9419. this._postRequest();
  9420. this.unsubscribe('iframe.onload', onload);
  9421. }, this);
  9422. this.subscribe('iframe.onload', onload);
  9423. }
  9424. return url;
  9425. },
  9426. /**
  9427. * Identifies the static resource that should be loaded
  9428. * if the widget is relying on a separate iframe to fetch
  9429. * its content with that of other widget-pipe-enabled widgets.
  9430. *
  9431. * @return {String} the URL of the widget pipe shell that
  9432. * contains the inlined JavaScript invoked when the
  9433. * widget pipe iframe has pulled its content and signal
  9434. * this iframe that the content is ready.
  9435. */
  9436. _getWidgetPipeShell: function() {
  9437. return FB.getDomain('www') + 'common/widget_pipe_shell.php';
  9438. },
  9439. /**
  9440. * Internal one time setup logic.
  9441. */
  9442. _oneTimeSetup: function() {
  9443. // the XD messages we want to handle. it is safe to subscribe to these even
  9444. // if they will not get used.
  9445. this.subscribe('xd.resize', FB.bind(this._handleResizeMsg, this));
  9446. // weak dependency on FB.Auth
  9447. if (FB.getLoginStatus) {
  9448. this.subscribe(
  9449. 'xd.refreshLoginStatus',
  9450. FB.bind(FB.getLoginStatus, FB, function(){}, true));
  9451. this.subscribe(
  9452. 'xd.logout',
  9453. FB.bind(FB.logout, FB, function(){}));
  9454. }
  9455. // setup forwarding of auth.statusChange events
  9456. if (this._refreshOnAuthChange) {
  9457. this._setupAuthRefresh();
  9458. }
  9459. // if we need to make it visible on iframe load
  9460. if (this._visibleAfter == 'load') {
  9461. this.subscribe('iframe.onload', FB.bind(this._makeVisible, this));
  9462. }
  9463. // hook for subclasses
  9464. this.oneTimeSetup();
  9465. },
  9466. /**
  9467. * Make the iframe visible and remove the loader.
  9468. */
  9469. _makeVisible: function() {
  9470. this._removeLoader();
  9471. FB.Dom.removeCss(this.dom, 'fb_hide_iframes');
  9472. this.fire('render');
  9473. },
  9474. /**
  9475. * Most iframe plugins do not tie their internal state to the "Connected"
  9476. * state of the application. In other words, the fan box knows who you are
  9477. * even if the page it contains does not. These plugins therefore only need
  9478. * to reload when the user signs in/out of facebook, not the application.
  9479. *
  9480. * This misses the case where the user switched logins without the
  9481. * application knowing about it. Unfortunately this is not possible/allowed.
  9482. */
  9483. _setupAuthRefresh: function() {
  9484. FB.getLoginStatus(FB.bind(function(response) {
  9485. var lastStatus = response.status;
  9486. FB.Event.subscribe('auth.statusChange', FB.bind(function(response) {
  9487. if (!this.isValid()) {
  9488. return;
  9489. }
  9490. // if we gained or lost a user, reprocess
  9491. if (lastStatus == 'unknown' || response.status == 'unknown') {
  9492. this.process(true);
  9493. }
  9494. lastStatus = response.status;
  9495. }, this));
  9496. }, this));
  9497. },
  9498. /**
  9499. * Invoked by the iframe when it wants to be resized.
  9500. */
  9501. _handleResizeMsg: function(message) {
  9502. if (!this.isValid()) {
  9503. return;
  9504. }
  9505. var iframe = this.getIframeNode();
  9506. iframe.style.height = message.height + 'px';
  9507. if (message.width) {
  9508. iframe.style.width = message.width + 'px';
  9509. }
  9510. iframe.style.border = 'none';
  9511. this._makeVisible();
  9512. },
  9513. /**
  9514. * Add the loader.
  9515. */
  9516. _addLoader: function() {
  9517. if (!this._loaderDiv) {
  9518. FB.Dom.addCss(this.dom, 'fb_iframe_widget_loader');
  9519. this._loaderDiv = document.createElement('div');
  9520. this._loaderDiv.className = 'FB_Loader';
  9521. this.dom.appendChild(this._loaderDiv);
  9522. }
  9523. },
  9524. /**
  9525. * Remove the loader.
  9526. */
  9527. _removeLoader: function() {
  9528. if (this._loaderDiv) {
  9529. FB.Dom.removeCss(this.dom, 'fb_iframe_widget_loader');
  9530. if (this._loaderDiv.parentNode) {
  9531. this._loaderDiv.parentNode.removeChild(this._loaderDiv);
  9532. }
  9533. this._loaderDiv = null;
  9534. }
  9535. },
  9536. /**
  9537. * Get's the final QS/Post Data for the iframe with automatic params added
  9538. * in.
  9539. *
  9540. * @return {Object} the params object
  9541. */
  9542. _getQS: function() {
  9543. return FB.copy({
  9544. api_key : FB._apiKey,
  9545. locale : FB._locale,
  9546. sdk : 'joey',
  9547. ref : this.getAttribute('ref')
  9548. }, this.getUrlBits().params);
  9549. },
  9550. /**
  9551. * Gets the final URL based on the name specified in the bits.
  9552. *
  9553. * @return {String} the url
  9554. */
  9555. _getURL: function() {
  9556. var
  9557. domain = this.getDefaultWebDomain(),
  9558. static_path = '';
  9559. if (this._fetchPreCachedLoader) {
  9560. domain = this.getDefaultStaticDomain();
  9561. static_path = 'static/';
  9562. }
  9563. return FB.getDomain(domain) + 'plugins/' + static_path +
  9564. this.getUrlBits().name + '.php';
  9565. },
  9566. /**
  9567. * Will do the POST request to the iframe.
  9568. */
  9569. _postRequest: function() {
  9570. FB.Content.submitToTarget({
  9571. url : this._getURL(),
  9572. target : this.getIframeNode().name,
  9573. params : this._getQS()
  9574. });
  9575. }
  9576. });
  9577. FB.provide('XFBML.IframeWidget', {
  9578. widgetPipeIframeCount: 0,
  9579. masterWidgetPipeIframe: null,
  9580. allWidgetPipeIframes: {},
  9581. batchWidgetPipeRequests: function() {
  9582. if (!FB.XFBML.IframeWidget.masterWidgetPipeIframe) {
  9583. // nothing widget-pipe enabled is being rendered,
  9584. // so ignore this entirely.
  9585. return;
  9586. }
  9587. var widgetPipeDescriptions =
  9588. FB.XFBML.IframeWidget._groupWidgetPipeDescriptions();
  9589. var widgetPipeParams = {
  9590. widget_pipe: FB.JSON.stringify(widgetPipeDescriptions),
  9591. href: window.location,
  9592. site: location.hostname,
  9593. channel: FB.XFBML.IframeWidget.masterWidgetPipeIframe.getChannelUrl(),
  9594. api_key: FB._apiKey,
  9595. locale: FB._locale,
  9596. sdk: 'joey'
  9597. };
  9598. var widgetPipeIframeName = FB.guid();
  9599. var masterWidgetPipeDom = FB.XFBML.IframeWidget.masterWidgetPipeIframe.dom;
  9600. // we need a dedicated span within the first fb:like tag to house the
  9601. // iframe that fetches all of the plugin data.
  9602. var masterWidgetPipeSpan =
  9603. masterWidgetPipeDom.appendChild(document.createElement('span'));
  9604. FB.Content.insertIframe({
  9605. url: 'about:blank',
  9606. root: masterWidgetPipeSpan,
  9607. name: widgetPipeIframeName,
  9608. className: 'fb_hidden fb_invisible',
  9609. onload: function() {
  9610. FB.Content.submitToTarget({
  9611. url: FB._domain.www + 'widget_pipe.php?widget_pipe=1',
  9612. target: widgetPipeIframeName,
  9613. params: widgetPipeParams
  9614. });
  9615. }
  9616. });
  9617. },
  9618. _groupWidgetPipeDescriptions: function() {
  9619. var widgetPipeDescriptions = {};
  9620. for (var key in FB.XFBML.IframeWidget.allWidgetPipeIframes) {
  9621. var controller = FB.XFBML.IframeWidget.allWidgetPipeIframes[key];
  9622. var urlBits = controller.getUrlBits();
  9623. var widgetPipeDescription = {
  9624. widget: urlBits.name
  9625. };
  9626. FB.copy(widgetPipeDescription, urlBits.params);
  9627. widgetPipeDescriptions[key] = widgetPipeDescription;
  9628. }
  9629. return widgetPipeDescriptions;
  9630. }
  9631. });
  9632. /**
  9633. * Copyright Facebook Inc.
  9634. *
  9635. * Licensed under the Apache License, Version 2.0 (the "License");
  9636. * you may not use this file except in compliance with the License.
  9637. * You may obtain a copy of the License at
  9638. *
  9639. * http://www.apache.org/licenses/LICENSE-2.0
  9640. *
  9641. * Unless required by applicable law or agreed to in writing, software
  9642. * distributed under the License is distributed on an "AS IS" BASIS,
  9643. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9644. * See the License for the specific language governing permissions and
  9645. * limitations under the License.
  9646. *
  9647. * @provides fb.xfbml.activity
  9648. * @layer xfbml
  9649. * @requires fb.type fb.xfbml.iframewidget
  9650. */
  9651. /**
  9652. * Implementation for fb:activity tag.
  9653. *
  9654. * @class FB.XFBML.Activity
  9655. * @extends FB.XFBML.IframeWidget
  9656. * @private
  9657. */
  9658. FB.subclass('XFBML.Activity', 'XFBML.IframeWidget', null, {
  9659. _visibleAfter: 'load',
  9660. /**
  9661. * Refresh the iframe on auth.statusChange events.
  9662. */
  9663. _refreshOnAuthChange: true,
  9664. /**
  9665. * Do initial attribute processing.
  9666. */
  9667. setupAndValidate: function() {
  9668. this._attr = {
  9669. border_color : this.getAttribute('border-color'),
  9670. colorscheme : this.getAttribute('color-scheme'),
  9671. filter : this.getAttribute('filter'),
  9672. action : this.getAttribute('action'),
  9673. max_age : this.getAttribute('max_age'),
  9674. font : this.getAttribute('font'),
  9675. linktarget : this.getAttribute('linktarget', '_blank'),
  9676. header : this._getBoolAttribute('header'),
  9677. height : this._getPxAttribute('height', 300),
  9678. recommendations : this._getBoolAttribute('recommendations'),
  9679. site : this.getAttribute('site', location.hostname),
  9680. width : this._getPxAttribute('width', 300)
  9681. };
  9682. return true;
  9683. },
  9684. /**
  9685. * Get the initial size.
  9686. *
  9687. * @return {Object} the size
  9688. */
  9689. getSize: function() {
  9690. return { width: this._attr.width, height: this._attr.height };
  9691. },
  9692. /**
  9693. * Get the URL bits for the iframe.
  9694. *
  9695. * @return {Object} the iframe URL bits
  9696. */
  9697. getUrlBits: function() {
  9698. return { name: 'activity', params: this._attr };
  9699. }
  9700. });
  9701. /**
  9702. * @provides fb.xfbml.buttonelement
  9703. * @requires fb.string
  9704. * fb.type
  9705. * fb.xfbml.element
  9706. * @css fb.css.button
  9707. * @layer xfbml
  9708. */
  9709. /**
  9710. * Copyright Facebook Inc.
  9711. *
  9712. * Licensed under the Apache License, Version 2.0 (the "License");
  9713. * you may not use this file except in compliance with the License.
  9714. * You may obtain a copy of the License at
  9715. *
  9716. * http://www.apache.org/licenses/LICENSE-2.0
  9717. *
  9718. * Unless required by applicable law or agreed to in writing, software
  9719. * distributed under the License is distributed on an "AS IS" BASIS,
  9720. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9721. * See the License for the specific language governing permissions and
  9722. * limitations under the License.
  9723. */
  9724. /**
  9725. * Base class for a button element.
  9726. *
  9727. * @class FB.XFBML.ButtonElement
  9728. * @extends FB.XFBML.Element
  9729. * @private
  9730. */
  9731. FB.subclass('XFBML.ButtonElement', 'XFBML.Element', null, {
  9732. _allowedSizes: ['icon', 'small', 'medium', 'large', 'xlarge'],
  9733. /////////////////////////////////////////////////////////////////////////////
  9734. // Methods the implementation MUST override
  9735. /////////////////////////////////////////////////////////////////////////////
  9736. /**
  9737. * Invoked when the button is clicked.
  9738. */
  9739. onClick: function() {
  9740. throw new Error('Inheriting class needs to implement onClick().');
  9741. },
  9742. /////////////////////////////////////////////////////////////////////////////
  9743. // Methods the implementation CAN override
  9744. /////////////////////////////////////////////////////////////////////////////
  9745. /**
  9746. * This method is invoked before any processing is done to do any initial
  9747. * setup and do any necessary validation on the attributes. A return value of
  9748. * false will indicate that validation was unsuccessful and processing will
  9749. * be halted. If you are going to return false and halt processing, you
  9750. * should ensure you use FB.log() to output a short informative message
  9751. * before doing so.
  9752. *
  9753. * @return {Boolean} true to continue processing, false to halt it
  9754. */
  9755. setupAndValidate: function() {
  9756. return true;
  9757. },
  9758. /**
  9759. * Should return the button markup. The default behaviour is to return the
  9760. * original innerHTML of the element.
  9761. *
  9762. * @return {String} the HTML markup for the button
  9763. */
  9764. getButtonMarkup: function() {
  9765. return this.getOriginalHTML();
  9766. },
  9767. /////////////////////////////////////////////////////////////////////////////
  9768. // Public methods the implementation CAN use
  9769. /////////////////////////////////////////////////////////////////////////////
  9770. /**
  9771. * Get the original innerHTML of the element.
  9772. *
  9773. * @return {String} the original innerHTML
  9774. */
  9775. getOriginalHTML: function() {
  9776. return this._originalHTML;
  9777. },
  9778. /////////////////////////////////////////////////////////////////////////////
  9779. // Private methods the implementation MUST NOT use or override
  9780. /////////////////////////////////////////////////////////////////////////////
  9781. /**
  9782. * Processes this tag.
  9783. */
  9784. process: function() {
  9785. if (!('_originalHTML' in this)) {
  9786. this._originalHTML = FB.String.trim(this.dom.innerHTML);
  9787. }
  9788. if (!this.setupAndValidate()) {
  9789. // failure to validate means we're done rendering what we can
  9790. this.fire('render');
  9791. return;
  9792. }
  9793. var
  9794. size = this._getAttributeFromList('size', 'medium', this._allowedSizes),
  9795. className = '',
  9796. markup = '';
  9797. if (size == 'icon') {
  9798. className = 'fb_button_simple';
  9799. } else {
  9800. var rtl_suffix = FB._localeIsRtl ? '_rtl' : '';
  9801. markup = this.getButtonMarkup();
  9802. className = 'fb_button' + rtl_suffix + ' fb_button_' + size + rtl_suffix;
  9803. }
  9804. if (markup !== '') {
  9805. this.dom.innerHTML = (
  9806. '<a class="' + className + '">' +
  9807. '<span class="fb_button_text">' + markup + '</span>' +
  9808. '</a>'
  9809. );
  9810. // the firstChild is the anchor tag we just setup above
  9811. this.dom.firstChild.onclick = FB.bind(this.onClick, this);
  9812. }
  9813. this.fire('render');
  9814. }
  9815. });
  9816. /**
  9817. * Copyright Facebook Inc.
  9818. *
  9819. * Licensed under the Apache License, Version 2.0 (the "License");
  9820. * you may not use this file except in compliance with the License.
  9821. * You may obtain a copy of the License at
  9822. *
  9823. * http://www.apache.org/licenses/LICENSE-2.0
  9824. *
  9825. * Unless required by applicable law or agreed to in writing, software
  9826. * distributed under the License is distributed on an "AS IS" BASIS,
  9827. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9828. * See the License for the specific language governing permissions and
  9829. * limitations under the License.
  9830. *
  9831. * @provides fb.xfbml.addprofiletab
  9832. * @layer xfbml
  9833. * @requires fb.type
  9834. * fb.intl
  9835. * fb.ui
  9836. * fb.xfbml.buttonelement
  9837. * fb.helper
  9838. */
  9839. /**
  9840. * Implementation for fb:add-profile-tab tag.
  9841. *
  9842. * @class FB.XFBML.AddProfileTab
  9843. * @extends FB.XFBML.ButtonElement
  9844. * @private
  9845. */
  9846. FB.subclass('XFBML.AddProfileTab', 'XFBML.ButtonElement', null, {
  9847. /**
  9848. * Should return the button markup. The default behaviour is to return the
  9849. * original innerHTML of the element.
  9850. *
  9851. * @return {String} the HTML markup for the button
  9852. */
  9853. getButtonMarkup: function() {
  9854. return FB.Intl.tx._("Add Profile Tab on Facebook");
  9855. },
  9856. /**
  9857. * The ButtonElement base class will invoke this when the button is clicked.
  9858. */
  9859. onClick: function() {
  9860. FB.ui({ method: 'profile.addtab' }, this.bind(function(result) {
  9861. if (result.tab_added) {
  9862. FB.Helper.invokeHandler(this.getAttribute('on-add'), this);
  9863. }
  9864. }));
  9865. }
  9866. });
  9867. /**
  9868. * Copyright Facebook Inc.
  9869. *
  9870. * Licensed under the Apache License, Version 2.0 (the "License");
  9871. * you may not use this file except in compliance with the License.
  9872. * You may obtain a copy of the License at
  9873. *
  9874. * http://www.apache.org/licenses/LICENSE-2.0
  9875. *
  9876. * Unless required by applicable law or agreed to in writing, software
  9877. * distributed under the License is distributed on an "AS IS" BASIS,
  9878. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9879. * See the License for the specific language governing permissions and
  9880. * limitations under the License.
  9881. *
  9882. * @provides fb.xfbml.facepile
  9883. * @layer xfbml
  9884. * @requires fb.type fb.auth
  9885. */
  9886. /**
  9887. * Implementation for fb:facepile tag.
  9888. *
  9889. * @class FB.XFBML.Facepile
  9890. * @extends FB.XFBML.Facepile
  9891. * @private
  9892. */
  9893. FB.subclass('XFBML.Facepile', 'XFBML.IframeWidget', null, {
  9894. _visibleAfter: 'load',
  9895. _extraParams: {},
  9896. /**
  9897. * Do initial attribute processing.
  9898. */
  9899. setupAndValidate: function() {
  9900. this._attr = {
  9901. href: this.getAttribute('href'),
  9902. channel: this.getChannelUrl(),
  9903. colorscheme: this.getAttribute('colorscheme', 'light'),
  9904. max_rows: this.getAttribute('max-rows'),
  9905. action: this.getAttribute('action', 'like'),
  9906. tense: this.getAttribute('tense', 'past'),
  9907. width: this._getPxAttribute('width', 200),
  9908. ref: this.getAttribute('ref'),
  9909. size: this.getAttribute('size', 'small'),
  9910. extended_social_context:
  9911. this.getAttribute('extended_social_context', false),
  9912. // inner html will become the login button text,
  9913. // if specified
  9914. login_text: this.dom.innerHTML
  9915. };
  9916. // clear out the inner html that we'll be using
  9917. // for the login button text
  9918. this.clear();
  9919. for (var key in this._extraParams) {
  9920. this._attr[key] = this._extraParams[key];
  9921. }
  9922. return true;
  9923. },
  9924. /**
  9925. * Sets extra parameters that will be passed to the widget's url.
  9926. */
  9927. setExtraParams: function(val) {
  9928. this._extraParams = val;
  9929. },
  9930. /**
  9931. * Setup event handlers.
  9932. */
  9933. oneTimeSetup: function() {
  9934. // this widget's internal state is tied to the "connected" status. it
  9935. // doesn't care about the difference between "unknown" and "notConnected".
  9936. var lastStatus = FB._userStatus;
  9937. FB.Event.subscribe('auth.statusChange', FB.bind(function(response) {
  9938. if (lastStatus == 'connected' || response.status == 'connected') {
  9939. this.process(true);
  9940. }
  9941. lastStatus = response.status;
  9942. }, this));
  9943. },
  9944. /**
  9945. * Get the initial size.
  9946. *
  9947. * By default, shows one row of 6 profiles
  9948. *
  9949. * @return {Object} the size
  9950. */
  9951. getSize: function() {
  9952. // made height to 90 to accommodate large profile pic sizes
  9953. if (this._attr.size == 'large') {
  9954. return { width: this._attr.width, height: 90 };
  9955. }
  9956. return { width: this._attr.width, height: 70 };
  9957. },
  9958. /**
  9959. * Get the URL bits for the iframe.
  9960. *
  9961. * @return {Object} the iframe URL bits
  9962. */
  9963. getUrlBits: function() {
  9964. return { name: 'facepile', params: this._attr };
  9965. }
  9966. });
  9967. /**
  9968. * Copyright Facebook Inc.
  9969. *
  9970. * Licensed under the Apache License, Version 2.0 (the "License");
  9971. * you may not use this file except in compliance with the License.
  9972. * You may obtain a copy of the License at
  9973. *
  9974. * http://www.apache.org/licenses/LICENSE-2.0
  9975. *
  9976. * Unless required by applicable law or agreed to in writing, software
  9977. * distributed under the License is distributed on an "AS IS" BASIS,
  9978. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9979. * See the License for the specific language governing permissions and
  9980. * limitations under the License.
  9981. *
  9982. * @provides fb.xfbml.addtotimeline
  9983. * @layer xfbml
  9984. * @requires fb.type fb.xfbml.iframewidget fb.xfbml.facepile fb.auth
  9985. */
  9986. /**
  9987. * Implementation for fb:add-to-timeline tag.
  9988. *
  9989. * @class FB.XFBML.AddToTimeline
  9990. * @extends FB.XFBML.IframeWidget
  9991. * @private
  9992. */
  9993. FB.subclass('XFBML.AddToTimeline', 'XFBML.Facepile', null, {
  9994. _visibleAfter: 'load',
  9995. /**
  9996. * Get the initial size.
  9997. *
  9998. * By default, 300x250
  9999. *
  10000. * @return {Object} the size
  10001. */
  10002. getSize: function() {
  10003. return { width: 300, height: 250 };
  10004. },
  10005. /**
  10006. * Get the URL bits for the iframe.
  10007. *
  10008. * @return {Object} the iframe URL bits
  10009. */
  10010. getUrlBits: function() {
  10011. return { name: 'add_to_timeline', params: this._attr };
  10012. }
  10013. });
  10014. /**
  10015. * Copyright Facebook Inc.
  10016. *
  10017. * Licensed under the Apache License, Version 2.0 (the "License");
  10018. * you may not use this file except in compliance with the License.
  10019. * You may obtain a copy of the License at
  10020. *
  10021. * http://www.apache.org/licenses/LICENSE-2.0
  10022. *
  10023. * Unless required by applicable law or agreed to in writing, software
  10024. * distributed under the License is distributed on an "AS IS" BASIS,
  10025. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10026. * See the License for the specific language governing permissions and
  10027. * limitations under the License.
  10028. *
  10029. * @provides fb.xfbml.bookmark
  10030. * @layer xfbml
  10031. * @requires fb.type
  10032. * fb.intl
  10033. * fb.ui
  10034. * fb.xfbml.buttonelement
  10035. * fb.helper
  10036. */
  10037. /**
  10038. * Implementation for fb:bookmark tag.
  10039. *
  10040. * @class FB.XFBML.Bookmark
  10041. * @extends FB.XFBML.ButtonElement
  10042. * @private
  10043. */
  10044. FB.subclass('XFBML.Bookmark', 'XFBML.ButtonElement', null, {
  10045. /**
  10046. * Should return the button markup. The default behaviour is to return the
  10047. * original innerHTML of the element.
  10048. *
  10049. * @return {String} the HTML markup for the button
  10050. */
  10051. getButtonMarkup: function() {
  10052. return FB.Intl.tx._("Bookmark on Facebook");
  10053. },
  10054. /**
  10055. * The ButtonElement base class will invoke this when the button is clicked.
  10056. */
  10057. onClick: function() {
  10058. FB.ui({ method: 'bookmark.add' }, this.bind(function(result) {
  10059. if (result.bookmarked) {
  10060. FB.Helper.invokeHandler(this.getAttribute('on-add'), this);
  10061. }
  10062. }));
  10063. }
  10064. });
  10065. /**
  10066. * Copyright Facebook Inc.
  10067. *
  10068. * Licensed under the Apache License, Version 2.0 (the "License");
  10069. * you may not use this file except in compliance with the License.
  10070. * You may obtain a copy of the License at
  10071. *
  10072. * http://www.apache.org/licenses/LICENSE-2.0
  10073. *
  10074. * Unless required by applicable law or agreed to in writing, software
  10075. * distributed under the License is distributed on an "AS IS" BASIS,
  10076. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10077. * See the License for the specific language governing permissions and
  10078. * limitations under the License.
  10079. *
  10080. * @provides fb.xfbml.comments
  10081. * @layer xfbml
  10082. * @requires fb.type fb.xfbml.iframewidget fb.auth fb.ua
  10083. */
  10084. /**
  10085. * Implementation for fb:comments tag.
  10086. *
  10087. * @class FB.XFBML.Comments
  10088. * @extends FB.XFBML.IframeWidget
  10089. * @private
  10090. */
  10091. FB.subclass('XFBML.Comments', 'XFBML.IframeWidget', null, {
  10092. _visibleAfter: 'immediate',
  10093. /**
  10094. * Refresh the iframe on auth.statusChange events.
  10095. */
  10096. _refreshOnAuthChange: true,
  10097. /**
  10098. * Do initial attribute processing.
  10099. */
  10100. setupAndValidate: function() {
  10101. // query parameters to the comments iframe
  10102. var attr = {
  10103. channel_url : this.getChannelUrl(),
  10104. colorscheme : this.getAttribute('colorscheme'),
  10105. numposts : this.getAttribute('num-posts', 10),
  10106. width : this._getPxAttribute('width', 550),
  10107. href : this.getAttribute('href'),
  10108. permalink : this.getAttribute('permalink'),
  10109. publish_feed : this.getAttribute('publish_feed'),
  10110. mobile : this._getBoolAttribute('mobile')
  10111. };
  10112. if (FB.initSitevars.enableMobileComments &&
  10113. FB.UA.mobile() &&
  10114. attr.mobile !== false) {
  10115. attr.mobile = true;
  10116. }
  10117. // legacy comments box params
  10118. if (!attr.href) {
  10119. attr.migrated = this.getAttribute('migrated');
  10120. attr.xid = this.getAttribute('xid');
  10121. attr.title = this.getAttribute('title', document.title);
  10122. attr.url = this.getAttribute('url', document.URL);
  10123. attr.quiet = this.getAttribute('quiet');
  10124. attr.reverse = this.getAttribute('reverse');
  10125. attr.simple = this.getAttribute('simple');
  10126. attr.css = this.getAttribute('css');
  10127. attr.notify = this.getAttribute('notify');
  10128. // default xid to current URL
  10129. if (!attr.xid) {
  10130. // We always want the URL minus the hash "#" also note the encoding here
  10131. // and down below when the url is built. This is intentional, so the
  10132. // string received by the server is url-encoded and thus valid.
  10133. var index = document.URL.indexOf('#');
  10134. if (index > 0) {
  10135. attr.xid = encodeURIComponent(document.URL.substring(0, index));
  10136. }
  10137. else {
  10138. attr.xid = encodeURIComponent(document.URL);
  10139. }
  10140. }
  10141. if (attr.migrated) {
  10142. attr.href =
  10143. 'http://www.facebook.com/plugins/comments_v1.php?' +
  10144. 'app_id=' + FB._apiKey +
  10145. '&xid=' + encodeURIComponent(attr.xid) +
  10146. '&url=' + encodeURIComponent(attr.url);
  10147. }
  10148. } else {
  10149. // allows deep linking of comments by surfacing linked comments
  10150. var fb_comment_id = this.getAttribute('fb_comment_id');
  10151. if (!fb_comment_id) {
  10152. fb_comment_id =
  10153. FB.QS.decode(
  10154. document.URL.substring(
  10155. document.URL.indexOf('?') + 1)).fb_comment_id;
  10156. if (fb_comment_id && fb_comment_id.indexOf('#') > 0) {
  10157. // strip out the hash if we managed to pick it up
  10158. fb_comment_id =
  10159. fb_comment_id.substring(0,
  10160. fb_comment_id.indexOf('#'));
  10161. }
  10162. }
  10163. if (fb_comment_id) {
  10164. attr.fb_comment_id = fb_comment_id;
  10165. this.subscribe('render',
  10166. FB.bind(function() {
  10167. window.location.hash = this.getIframeNode().id;
  10168. }, this));
  10169. }
  10170. }
  10171. this._attr = attr;
  10172. return true;
  10173. },
  10174. /**
  10175. * Setup event handlers.
  10176. */
  10177. oneTimeSetup: function() {
  10178. this.subscribe('xd.addComment',
  10179. FB.bind(this._handleCommentMsg, this));
  10180. this.subscribe('xd.commentCreated',
  10181. FB.bind(this._handleCommentCreatedMsg, this));
  10182. this.subscribe('xd.commentRemoved',
  10183. FB.bind(this._handleCommentRemovedMsg, this));
  10184. },
  10185. /**
  10186. * Get the initial size.
  10187. *
  10188. * @return {Object} the size
  10189. */
  10190. getSize: function() {
  10191. if (this._attr.mobile) {
  10192. return { width: '100%', height: 160 };
  10193. }
  10194. return { width: this._attr.width, height: 160 };
  10195. },
  10196. /**
  10197. * Get the URL bits for the iframe.
  10198. *
  10199. * @return {Object} the iframe URL bits
  10200. */
  10201. getUrlBits: function() {
  10202. return { name: 'comments', params: this._attr };
  10203. },
  10204. /**
  10205. * Returns the default domain that should be used for the
  10206. * comments plugin being served from the web tier. Because
  10207. * of the complexities involved in serving up the comments
  10208. * plugin for logged out HTTPS users, and because a near-majority
  10209. * of comments plugins are rendered for logged out users, we
  10210. * just always load the comments plugin over https.
  10211. */
  10212. getDefaultWebDomain: function() {
  10213. if (this._attr.mobile) {
  10214. return 'https_m';
  10215. } else {
  10216. return 'https_www';
  10217. }
  10218. },
  10219. /**
  10220. * Invoked by the iframe when a comment is added. Note, this feature needs to
  10221. * be enabled by specifying the notify=true attribute on the tag. This is in
  10222. * order to improve performance by only requiring this overhead when a
  10223. * developer explicitly said they want it.
  10224. *
  10225. * @param message {Object} the message received via XD
  10226. */
  10227. _handleCommentMsg: function(message) {
  10228. //TODO (naitik) what should we be giving the developers here? is there a
  10229. // comment_id they can get?
  10230. if (!this.isValid()) {
  10231. return;
  10232. }
  10233. FB.Event.fire('comments.add', {
  10234. post: message.post,
  10235. user: message.user,
  10236. widget: this
  10237. });
  10238. },
  10239. _handleCommentCreatedMsg: function(message) {
  10240. if (!this.isValid()) {
  10241. return;
  10242. }
  10243. var eventArgs = {
  10244. href: message.href,
  10245. commentID: message.commentID,
  10246. parentCommentID: message.parentCommentID
  10247. };
  10248. FB.Event.fire('comment.create', eventArgs);
  10249. },
  10250. _handleCommentRemovedMsg: function(message) {
  10251. if (!this.isValid()) {
  10252. return;
  10253. }
  10254. var eventArgs = {
  10255. href: message.href,
  10256. commentID: message.commentID
  10257. };
  10258. FB.Event.fire('comment.remove', eventArgs);
  10259. }
  10260. });
  10261. /**
  10262. * @provides fb.xfbml.commentscount
  10263. * @requires fb.data
  10264. * fb.dom
  10265. * fb.helper
  10266. * fb.intl
  10267. * fb.string
  10268. * fb.type
  10269. * fb.ui
  10270. * fb.xfbml
  10271. * fb.xfbml.element
  10272. * @css fb.css.sharebutton
  10273. * @layer xfbml
  10274. */
  10275. /**
  10276. * Copyright Facebook Inc.
  10277. *
  10278. * Licensed under the Apache License, Version 2.0 (the "License");
  10279. * you may not use this file except in compliance with the License.
  10280. * You may obtain a copy of the License at
  10281. *
  10282. * http://www.apache.org/licenses/LICENSE-2.0
  10283. *
  10284. * Unless required by applicable law or agreed to in writing, software
  10285. * distributed under the License is distributed on an "AS IS" BASIS,
  10286. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10287. * See the License for the specific language governing permissions and
  10288. * limitations under the License.
  10289. */
  10290. /**
  10291. * Implementation for fb:comments-count tag.
  10292. * @class FB.XFBML.CommentsCount
  10293. * @extends FB.XFBML.Element
  10294. * @private
  10295. */
  10296. FB.subclass('XFBML.CommentsCount', 'XFBML.Element', null, {
  10297. /**
  10298. * Processes this tag.
  10299. */
  10300. process: function() {
  10301. this._href = this.getAttribute('href', window.location.href);
  10302. this._count = FB.Data._selectByIndex(
  10303. ['commentsbox_count'],
  10304. 'link_stat',
  10305. 'url',
  10306. this._href
  10307. );
  10308. FB.Dom.addCss(this.dom, 'fb_comments_count_zero');
  10309. this._count.wait(
  10310. FB.bind(
  10311. function() {
  10312. var c = this._count.value[0].commentsbox_count;
  10313. this.dom.innerHTML = FB.String.format(
  10314. '<span class="fb_comments_count">{0}</span>',
  10315. c);
  10316. if (c > 0) {
  10317. FB.Dom.removeCss(this.dom, 'fb_comments_count_zero');
  10318. }
  10319. this.fire('render');
  10320. },
  10321. this)
  10322. );
  10323. }
  10324. });
  10325. /**
  10326. * Copyright Facebook Inc.
  10327. *
  10328. * Licensed under the Apache License, Version 2.0 (the "License");
  10329. * you may not use this file except in compliance with the License.
  10330. * You may obtain a copy of the License at
  10331. *
  10332. * http://www.apache.org/licenses/LICENSE-2.0
  10333. *
  10334. * Unless required by applicable law or agreed to in writing, software
  10335. * distributed under the License is distributed on an "AS IS" BASIS,
  10336. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10337. * See the License for the specific language governing permissions and
  10338. * limitations under the License.
  10339. *
  10340. * @provides fb.anim
  10341. * @layer basic
  10342. * @requires fb.prelude fb.array fb.dom
  10343. */
  10344. /**
  10345. * This provides helper methods related to basic animation.
  10346. *
  10347. * @class FB.Anim
  10348. * @static
  10349. * @private
  10350. */
  10351. FB.provide('Anim', {
  10352. /**
  10353. * Animate Transformable Element
  10354. *
  10355. * Note: only pixel, point, %, and opactity values are animate-able
  10356. *
  10357. * @param dom {DOMElement} the element to be animated
  10358. * @param props {Object} an object with the properties of the animation
  10359. * destination
  10360. * @param duration {Number} the number of milliseconds over which the
  10361. * animation should happen.
  10362. * @param callback {Function} the callback function to call after the
  10363. * animation is complete
  10364. */
  10365. ate: function(dom, props, duration, callback) {
  10366. duration = !isNaN(parseFloat(duration)) && duration >= 0 ? duration : 750;
  10367. var
  10368. frame_speed = 40,
  10369. from = {},
  10370. to = {},
  10371. begin = null,
  10372. s = dom.style,
  10373. timer = setInterval(FB.bind(function() {
  10374. if (!begin) { begin = new Date().getTime(); }
  10375. // percent done
  10376. var pd = 1;
  10377. if (duration != 0) {
  10378. pd = Math.min((new Date().getTime() - begin) / duration, 1);
  10379. }
  10380. FB.Array.forEach(props, FB.bind(function(value, prop) {
  10381. if (!from[prop]) { // parse from CSS
  10382. var style = FB.Dom.getStyle(dom, prop);
  10383. // check for can't animate this, bad prop for this browser
  10384. if (style === false) { return; }
  10385. from[prop] = this._parseCSS(style+''); // force string type
  10386. }
  10387. if (!to[prop]) { // parse to CSS
  10388. to[prop] = this._parseCSS(value.toString());
  10389. }
  10390. var next = ''; // the next value to set
  10391. FB.Array.forEach(from[prop], function(pair, i) {
  10392. /* check for user override not animating this part via special
  10393. * symbol, "?". This is best used for animating properties with
  10394. * multiple parts, such as backgroundPosition, where you only want
  10395. * to animate one part and not the other.
  10396. *
  10397. * e.g.
  10398. * backgroundPosition: '8px 10px' => moves x and y to 8, 10
  10399. * backgroundPosition: '? 4px' => moves y to 4 and leaves x alone
  10400. * backgroundPosition: '7px ?' => moves x to 7 and leaves y alone
  10401. */
  10402. if (isNaN(to[prop][i].numPart) && to[prop][i].textPart == '?') {
  10403. next = pair.numPart + pair.textPart;
  10404. /* check for a non animate-able part
  10405. * this includes colors (for now), positions, anything with out a #,
  10406. * etc.
  10407. */
  10408. } else if (isNaN(pair.numPart)) {
  10409. next = pair.textPart;
  10410. // yay it's animate-able!
  10411. } else {
  10412. next +=
  10413. (pair.numPart + // orig value
  10414. Math.ceil((to[prop][i].numPart - pair.numPart) *
  10415. Math.sin(Math.PI/2 * pd))) +
  10416. to[prop][i].textPart + ' '; // text part and trailing space
  10417. }
  10418. });
  10419. // update with new value
  10420. FB.Dom.setStyle(dom, prop, next);
  10421. }, this));
  10422. if (pd == 1) { // are we done? clear the timer, call the callback
  10423. clearInterval(timer);
  10424. if (callback) { callback(dom); }
  10425. }
  10426. }, this), frame_speed);
  10427. },
  10428. /*
  10429. * Parses a CSS statment into it's parts
  10430. *
  10431. * e.g. "1px solid black" =>
  10432. * [[numPart: 1, textPart: 'px'],
  10433. * [numPart: NaN, textPart: 'solid'],
  10434. * [numPart: NaN, textPart: 'black']]
  10435. * or
  10436. * "5px 0% 2em none" =>
  10437. * [[numPart: 5, textPart: 'px'],
  10438. * [numPart: 0, textPart: '%'],
  10439. * [numPart: 2, textPart: 'em'],
  10440. * [numPart: NaN, textPart: 'none']]
  10441. */
  10442. _parseCSS: function(css) {
  10443. var ret = [];
  10444. FB.Array.forEach(css.split(' '), function(peice) {
  10445. var num = parseInt(peice, 10);
  10446. ret.push({numPart: num, textPart: peice.replace(num,'')});
  10447. });
  10448. return ret;
  10449. }
  10450. });
  10451. /**
  10452. * Copyright Facebook Inc.
  10453. *
  10454. * Licensed under the Apache License, Version 2.0 (the "License");
  10455. * you may not use this file except in compliance with the License.
  10456. * You may obtain a copy of the License at
  10457. *
  10458. * http://www.apache.org/licenses/LICENSE-2.0
  10459. *
  10460. * Unless required by applicable law or agreed to in writing, software
  10461. * distributed under the License is distributed on an "AS IS" BASIS,
  10462. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10463. * See the License for the specific language governing permissions and
  10464. * limitations under the License.
  10465. *
  10466. *
  10467. *
  10468. * Contains the public method ``FB.Insights.impression`` for analytics pixel
  10469. *
  10470. * @provides fb.insights
  10471. * @requires fb.prelude
  10472. */
  10473. /**
  10474. * Analytics pixel calls. If you are unsure about the potential that
  10475. * integrating Facebook could provide your application, you can use this light
  10476. * weight image beacon to collect some insights.
  10477. *
  10478. * TODO: Where does one go to look at this data?
  10479. *
  10480. * @class FB.Insights
  10481. * @static
  10482. * @access private
  10483. */
  10484. FB.provide('Insights', {
  10485. /**
  10486. * This method should be called once by each page where you want to track
  10487. * impressions.
  10488. *
  10489. * FB.Insights.impression(
  10490. * {
  10491. * api_key: 'API_KEY',
  10492. * lid: 'EVENT_TYPE'
  10493. * }
  10494. * );
  10495. *
  10496. * @access private
  10497. * @param params {Object} parameters for the impression
  10498. * @param cb {Function} optional - called with the result of the action
  10499. */
  10500. impression: function(params, cb) {
  10501. // no http or https so browser will use protocol of current page
  10502. // see http://www.faqs.org/rfcs/rfc1808.html
  10503. var g = FB.guid(),
  10504. u = "//ah8.facebook.com/impression.php/" + g + "/",
  10505. i = new Image(1, 1),
  10506. s = [];
  10507. if (!params.api_key && FB._apiKey) {
  10508. params.api_key = FB._apiKey;
  10509. }
  10510. for (var k in params) {
  10511. s.push(encodeURIComponent(k) + '=' + encodeURIComponent(params[k]));
  10512. }
  10513. u += '?' + s.join('&');
  10514. if (cb) {
  10515. i.onload = cb;
  10516. }
  10517. i.src = u;
  10518. }
  10519. });
  10520. /**
  10521. * @provides fb.xfbml.connectbar
  10522. * @requires fb.anim
  10523. * fb.auth
  10524. * fb.data
  10525. * fb.dom
  10526. * fb.event
  10527. * fb.helper
  10528. * fb.insights
  10529. * fb.intl
  10530. * fb.string
  10531. * fb.type
  10532. * fb.ua
  10533. * fb.xfbml
  10534. * fb.xfbml.element
  10535. * @css fb.css.connectbarwidget
  10536. * @layer xfbml
  10537. */
  10538. /**
  10539. * Copyright Facebook Inc.
  10540. *
  10541. * Licensed under the Apache License, Version 2.0 (the "License");
  10542. * you may not use this file except in compliance with the License.
  10543. * You may obtain a copy of the License at
  10544. *
  10545. * http://www.apache.org/licenses/LICENSE-2.0
  10546. *
  10547. * Unless required by applicable law or agreed to in writing, software
  10548. * distributed under the License is distributed on an "AS IS" BASIS,
  10549. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10550. * See the License for the specific language governing permissions and
  10551. * limitations under the License.
  10552. */
  10553. /**
  10554. * @class FB.XFBML.ConnectBar
  10555. * @extends FB.XFBML.Element
  10556. * @private
  10557. */
  10558. FB.subclass('XFBML.ConnectBar', 'XFBML.Element', null, {
  10559. _initialHeight: null,
  10560. _initTopMargin: 0,
  10561. _picFieldName: 'pic_square',
  10562. _page: null, // the external site's content parent node
  10563. _displayed: false, // is the bar currently displayed
  10564. _notDisplayed: false, // is the bar currently not displayed
  10565. _container: null,
  10566. _animationSpeed: 0, // default to no (zero time, instant) animation
  10567. /**
  10568. * Processes this tag.
  10569. */
  10570. process: function() {
  10571. // Wait for status to be known
  10572. FB.getLoginStatus(this.bind(function(resp) {
  10573. FB.Event.monitor('auth.statusChange', this.bind(function() {
  10574. // Is Element still in DOM tree? are we connected?
  10575. if (this.isValid() && FB._userStatus == 'connected') {
  10576. this._uid = FB.Helper.getLoggedInUser();
  10577. FB.api({ // check if marked seen / current seen count
  10578. method: 'Connect.shouldShowConnectBar'
  10579. }, this.bind(function(showBar) {
  10580. if (showBar != 2) {
  10581. this._animationSpeed = (showBar == 0) ? 750 : 0;
  10582. this._showBar();
  10583. } else {
  10584. this._noRender();
  10585. }
  10586. }));
  10587. } else {
  10588. this._noRender();
  10589. }
  10590. return false; // continue monitoring
  10591. }));
  10592. }));
  10593. },
  10594. /**
  10595. * load the data for the bar and render it firing all the events in the
  10596. * process
  10597. */
  10598. _showBar: function() {
  10599. var q1 = FB.Data._selectByIndex(['first_name', 'profile_url',
  10600. this._picFieldName],
  10601. 'user', 'uid', this._uid);
  10602. var q2 = FB.Data._selectByIndex(['display_name'], 'application',
  10603. 'api_key', FB._apiKey);
  10604. FB.Data.waitOn([q1, q2], FB.bind(function(data) {
  10605. data[0][0].site_name = data[1][0].display_name;
  10606. if (!this._displayed) {
  10607. this._displayed = true;
  10608. this._notDisplayed = false;
  10609. this._renderConnectBar(data[0][0]);
  10610. this.fire('render');
  10611. FB.Insights.impression({
  10612. lid: 104,
  10613. name: 'widget_load'
  10614. });
  10615. this.fire('connectbar.ondisplay');
  10616. FB.Event.fire('connectbar.ondisplay', this);
  10617. FB.Helper.invokeHandler(this.getAttribute('on-display'), this);
  10618. }
  10619. }, this));
  10620. },
  10621. /**
  10622. * If the bar is rendered, hide it and fire the no render events
  10623. */
  10624. _noRender: function() {
  10625. if (this._displayed) {
  10626. this._displayed = false;
  10627. this._closeConnectBar();
  10628. }
  10629. if (!this._notDisplayed) {
  10630. this._notDisplayed = true;
  10631. this.fire('render');
  10632. this.fire('connectbar.onnotdisplay');
  10633. FB.Event.fire('connectbar.onnotdisplay', this);
  10634. FB.Helper.invokeHandler(this.getAttribute('on-not-display'), this);
  10635. }
  10636. },
  10637. /**
  10638. * Given this name, site name, and profile pic url render the connect bar
  10639. */
  10640. _renderConnectBar: function(info) {
  10641. var bar = document.createElement('div'),
  10642. container = document.createElement('div');
  10643. // TODO(alpjor) add rtl support
  10644. bar.className = 'fb_connect_bar';
  10645. container.className = 'fb_reset fb_connect_bar_container';
  10646. container.appendChild(bar);
  10647. document.body.appendChild(container);
  10648. this._container = container;
  10649. this._initialHeight = Math.round(
  10650. parseFloat(FB.Dom.getStyle(container, 'height')) +
  10651. parseFloat(FB.Dom.getStyle(container, 'borderBottomWidth')));
  10652. bar.innerHTML = FB.String.format(
  10653. '<div class="fb_buttons">' +
  10654. '<a href="#" class="fb_bar_close">' +
  10655. '<img src="{1}" alt="{2}" title="{2}"/>' +
  10656. '</a>' +
  10657. '</div>' +
  10658. '<a href="{7}" class="fb_profile" target="_blank">' +
  10659. '<img src="{3}" alt="{4}" title="{4}"/>' +
  10660. '</a>' +
  10661. '{5}' +
  10662. ' <span>' +
  10663. '<a href="{8}" class="fb_learn_more" target="_blank">{6}</a> &ndash; ' +
  10664. '<a href="#" class="fb_no_thanks">{0}</a>' +
  10665. '</span>',
  10666. FB.Intl.tx._("No Thanks"),
  10667. FB.getDomain('cdn') + FB.XFBML.ConnectBar.imgs.buttonUrl,
  10668. FB.Intl.tx._("Close"),
  10669. info[this._picFieldName] || FB.getDomain('cdn') +
  10670. FB.XFBML.ConnectBar.imgs.missingProfileUrl,
  10671. FB.String.escapeHTML(info.first_name),
  10672. FB.Intl.tx._("Hi {firstName}. \u003Cstrong>{siteName}\u003C\/strong> is using Facebook to personalize your experience.", {
  10673. firstName: FB.String.escapeHTML(info.first_name),
  10674. siteName: FB.String.escapeHTML(info.site_name)
  10675. }),
  10676. FB.Intl.tx._("Learn More"),
  10677. info.profile_url,
  10678. FB.getDomain('www') + 'sitetour/connect.php'
  10679. );
  10680. var _this = this;
  10681. FB.Array.forEach(bar.getElementsByTagName('a'), function(el) {
  10682. el.onclick = FB.bind(_this._clickHandler, _this);
  10683. });
  10684. this._page = document.body;
  10685. var top_margin = 0;
  10686. if (this._page.parentNode) {
  10687. top_margin = Math.round(
  10688. (parseFloat(FB.Dom.getStyle(this._page.parentNode, 'height')) -
  10689. parseFloat(FB.Dom.getStyle(this._page, 'height'))) / 2);
  10690. } else {
  10691. top_margin = parseInt(FB.Dom.getStyle(this._page, 'marginTop'), 10);
  10692. }
  10693. top_margin = isNaN(top_margin) ? 0 : top_margin;
  10694. this._initTopMargin = top_margin;
  10695. if (!window.XMLHttpRequest) { // ie6
  10696. container.className += " fb_connect_bar_container_ie6";
  10697. } else {
  10698. container.style.top = (-1*this._initialHeight) + 'px';
  10699. FB.Anim.ate(container, { top: '0px' }, this._animationSpeed);
  10700. }
  10701. var move = { marginTop: this._initTopMargin + this._initialHeight + 'px' }
  10702. if (FB.UA.ie()) { // for ie
  10703. move.backgroundPositionY = this._initialHeight + 'px'
  10704. } else { // for others
  10705. move.backgroundPosition = '? ' + this._initialHeight + 'px'
  10706. }
  10707. FB.Anim.ate(this._page, move, this._animationSpeed);
  10708. },
  10709. /**
  10710. * Handle the anchor clicks from the connect bar
  10711. *
  10712. */
  10713. _clickHandler : function(e) {
  10714. e = e || window.event;
  10715. var el = e.target || e.srcElement;
  10716. while (el.nodeName != 'A') { el = el.parentNode; }
  10717. switch (el.className) {
  10718. case 'fb_bar_close':
  10719. FB.api({ // mark seen
  10720. method: 'Connect.connectBarMarkAcknowledged'
  10721. });
  10722. FB.Insights.impression({
  10723. lid: 104,
  10724. name: 'widget_user_closed'
  10725. });
  10726. this._closeConnectBar();
  10727. break;
  10728. case 'fb_learn_more':
  10729. case 'fb_profile':
  10730. window.open(el.href);
  10731. break;
  10732. case 'fb_no_thanks':
  10733. this._closeConnectBar();
  10734. FB.api({ // mark seen
  10735. method: 'Connect.connectBarMarkAcknowledged'
  10736. });
  10737. FB.Insights.impression({
  10738. lid: 104,
  10739. name: 'widget_user_no_thanks'
  10740. });
  10741. FB.api({ method: 'auth.revokeAuthorization', block: true },
  10742. this.bind(function() {
  10743. this.fire('connectbar.ondeauth');
  10744. FB.Event.fire('connectbar.ondeauth', this);
  10745. FB.Helper.invokeHandler(this.getAttribute('on-deauth'), this);
  10746. if (this._getBoolAttribute('auto-refresh', true)) {
  10747. window.location.reload();
  10748. }
  10749. }));
  10750. break;
  10751. }
  10752. return false;
  10753. },
  10754. _closeConnectBar: function() {
  10755. this._notDisplayed = true;
  10756. var move = { marginTop: this._initTopMargin + 'px' }
  10757. if (FB.UA.ie()) { // for ie
  10758. move.backgroundPositionY = '0px'
  10759. } else { // for others
  10760. move.backgroundPosition = '? 0px'
  10761. }
  10762. var speed = (this._animationSpeed == 0) ? 0 : 300;
  10763. FB.Anim.ate(this._page, move, speed);
  10764. FB.Anim.ate(this._container, {
  10765. top: (-1 * this._initialHeight) + 'px'
  10766. }, speed, function(el) {
  10767. el.parentNode.removeChild(el);
  10768. });
  10769. this.fire('connectbar.onclose');
  10770. FB.Event.fire('connectbar.onclose', this);
  10771. FB.Helper.invokeHandler(this.getAttribute('on-close'), this);
  10772. }
  10773. });
  10774. FB.provide('XFBML.ConnectBar', {
  10775. imgs: {
  10776. buttonUrl: 'images/facebook-widgets/close_btn.png',
  10777. missingProfileUrl: 'pics/q_silhouette.gif'
  10778. }
  10779. });
  10780. /**
  10781. * Copyright Facebook Inc.
  10782. *
  10783. * Licensed under the Apache License, Version 2.0 (the "License");
  10784. * you may not use this file except in compliance with the License.
  10785. * You may obtain a copy of the License at
  10786. *
  10787. * http://www.apache.org/licenses/LICENSE-2.0
  10788. *
  10789. * Unless required by applicable law or agreed to in writing, software
  10790. * distributed under the License is distributed on an "AS IS" BASIS,
  10791. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10792. * See the License for the specific language governing permissions and
  10793. * limitations under the License.
  10794. *
  10795. * @provides fb.xfbml.fan
  10796. * @layer xfbml
  10797. * @requires fb.type fb.xfbml.iframewidget
  10798. */
  10799. /**
  10800. * Implementation for fb:fan tag.
  10801. *
  10802. * @class FB.XFBML.Fan
  10803. * @extends FB.XFBML.IframeWidget
  10804. * @private
  10805. */
  10806. FB.subclass('XFBML.Fan', 'XFBML.IframeWidget', null, {
  10807. _visibleAfter: 'load',
  10808. /**
  10809. * Do initial attribute processing.
  10810. */
  10811. setupAndValidate: function() {
  10812. this._attr = {
  10813. api_key : FB._apiKey,
  10814. connections : this.getAttribute('connections', '10'),
  10815. css : this.getAttribute('css'),
  10816. height : this._getPxAttribute('height'),
  10817. id : this.getAttribute('profile-id'),
  10818. logobar : this._getBoolAttribute('logo-bar'),
  10819. name : this.getAttribute('name'),
  10820. stream : this._getBoolAttribute('stream', true),
  10821. width : this._getPxAttribute('width', 300)
  10822. };
  10823. // "id" or "name" is required
  10824. if (!this._attr.id && !this._attr.name) {
  10825. FB.log('<fb:fan> requires one of the "id" or "name" attributes.');
  10826. return false;
  10827. }
  10828. var height = this._attr.height;
  10829. if (!height) {
  10830. if ((!this._attr.connections || this._attr.connections === '0') &&
  10831. !this._attr.stream) {
  10832. height = 65;
  10833. } else if (!this._attr.connections || this._attr.connections === '0') {
  10834. height = 375;
  10835. } else if (!this._attr.stream) {
  10836. height = 250;
  10837. } else {
  10838. height = 550;
  10839. }
  10840. }
  10841. // add space for logobar
  10842. if (this._attr.logobar) {
  10843. height += 25;
  10844. }
  10845. this._attr.height = height;
  10846. return true;
  10847. },
  10848. /**
  10849. * Get the initial size.
  10850. *
  10851. * @return {Object} the size
  10852. */
  10853. getSize: function() {
  10854. return { width: this._attr.width, height: this._attr.height };
  10855. },
  10856. /**
  10857. * Get the URL bits for the iframe.
  10858. *
  10859. * @return {Object} the iframe URL bits
  10860. */
  10861. getUrlBits: function() {
  10862. return { name: 'fan', params: this._attr };
  10863. }
  10864. });
  10865. /**
  10866. * Copyright Facebook Inc.
  10867. *
  10868. * Licensed under the Apache License, Version 2.0 (the "License");
  10869. * you may not use this file except in compliance with the License.
  10870. * You may obtain a copy of the License at
  10871. *
  10872. * http://www.apache.org/licenses/LICENSE-2.0
  10873. *
  10874. * Unless required by applicable law or agreed to in writing, software
  10875. * distributed under the License is distributed on an "AS IS" BASIS,
  10876. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10877. * See the License for the specific language governing permissions and
  10878. * limitations under the License.
  10879. *
  10880. * @provides fb.xfbml.friendpile
  10881. * @layer xfbml
  10882. * @requires fb.type fb.xfbml.facepile
  10883. */
  10884. /**
  10885. * Implementation for fb:friendpile tag.
  10886. *
  10887. * @class FB.XFBML.Friendpile
  10888. * @extends FB.XFBML.Friendpile
  10889. * @private
  10890. */
  10891. FB.subclass('XFBML.Friendpile', 'XFBML.Facepile', null, {});
  10892. /**
  10893. * @provides fb.xfbml.edgecommentwidget
  10894. * @requires fb.type
  10895. * fb.xfbml.iframewidget
  10896. * @css fb.css.edgecommentwidget
  10897. * @layer xfbml
  10898. */
  10899. /**
  10900. * Copyright Facebook Inc.
  10901. *
  10902. * Licensed under the Apache License, Version 2.0 (the "License");
  10903. * you may not use this file except in compliance with the License.
  10904. * You may obtain a copy of the License at
  10905. *
  10906. * http://www.apache.org/licenses/LICENSE-2.0
  10907. *
  10908. * Unless required by applicable law or agreed to in writing, software
  10909. * distributed under the License is distributed on an "AS IS" BASIS,
  10910. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10911. * See the License for the specific language governing permissions and
  10912. * limitations under the License.
  10913. */
  10914. /**
  10915. * Base implementation for Edge Comment Widgets.
  10916. *
  10917. * @class FB.XFBML.EdgeCommentWidget
  10918. * @extends FB.XFBML.IframeWidget
  10919. * @private
  10920. */
  10921. FB.subclass('XFBML.EdgeCommentWidget', 'XFBML.IframeWidget',
  10922. function(opts) {
  10923. this._iframeWidth = opts.width + 1;
  10924. this._iframeHeight = opts.height;
  10925. this._attr = {
  10926. master_frame_name: opts.masterFrameName,
  10927. offsetX: opts.relativeWidthOffset - opts.paddingLeft
  10928. };
  10929. this.dom = opts.commentNode;
  10930. this.dom.style.top = opts.relativeHeightOffset + 'px';
  10931. this.dom.style.left = opts.relativeWidthOffset + 'px';
  10932. this.dom.style.zIndex = FB.XFBML.EdgeCommentWidget.NextZIndex++;
  10933. FB.Dom.addCss(this.dom, 'fb_edge_comment_widget');
  10934. }, {
  10935. /////////////////////////////////////////////////////////////////////////////
  10936. // Internal stuff.
  10937. /////////////////////////////////////////////////////////////////////////////
  10938. /**
  10939. * Make the iframe visible only when it has finished loading.
  10940. */
  10941. _visibleAfter: 'load',
  10942. _showLoader: false,
  10943. /**
  10944. * Get the initial size.
  10945. *
  10946. * @return {Object} the size
  10947. */
  10948. getSize: function() {
  10949. return {
  10950. width: this._iframeWidth,
  10951. height: this._iframeHeight
  10952. };
  10953. },
  10954. /**
  10955. * Get there URL bits for the iframe.
  10956. *
  10957. * @return {Object} the iframe URL bits.
  10958. */
  10959. getUrlBits: function() {
  10960. return { name: 'comment_widget_shell', params: this._attr };
  10961. }
  10962. });
  10963. FB.provide('XFBML.EdgeCommentWidget', {
  10964. NextZIndex : 10000
  10965. });
  10966. /**
  10967. * Copyright Facebook Inc.
  10968. *
  10969. * Licensed under the Apache License, Version 2.0 (the "License");
  10970. * you may not use this file except in compliance with the License.
  10971. * You may obtain a copy of the License at
  10972. *
  10973. * http://www.apache.org/licenses/LICENSE-2.0
  10974. *
  10975. * Unless required by applicable law or agreed to in writing, software
  10976. * distributed under the License is distributed on an "AS IS" BASIS,
  10977. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10978. * See the License for the specific language governing permissions and
  10979. * limitations under the License.
  10980. *
  10981. * @provides fb.xfbml.edgewidget
  10982. * @layer xfbml
  10983. * @requires fb.type
  10984. * fb.dom
  10985. * fb.event
  10986. * fb.helper
  10987. * fb.xfbml.iframewidget
  10988. * fb.xfbml.edgecommentwidget
  10989. */
  10990. /**
  10991. * Base implementation for Edge Widgets.
  10992. *
  10993. * @class FB.XFBML.EdgeWidget
  10994. * @extends FB.XFBML.IframeWidget
  10995. * @private
  10996. */
  10997. FB.subclass('XFBML.EdgeWidget', 'XFBML.IframeWidget', null, {
  10998. /**
  10999. * Make the iframe visible only when it has finished loading.
  11000. */
  11001. _visibleAfter: 'immediate',
  11002. _showLoader: false,
  11003. _rootPadding: null,
  11004. /**
  11005. * Do initial attribute processing.
  11006. */
  11007. setupAndValidate : function() {
  11008. FB.Dom.addCss(this.dom, 'fb_edge_widget_with_comment');
  11009. this._attr = {
  11010. channel_url : this.getChannelUrl(),
  11011. debug : this._getBoolAttribute('debug'),
  11012. href : this.getAttribute('href', window.location.href),
  11013. is_permalink : this._getBoolAttribute('is-permalink'),
  11014. node_type : this.getAttribute('node-type', 'link'),
  11015. width : this._getWidgetWidth(),
  11016. font : this.getAttribute('font'),
  11017. layout : this._getLayout(),
  11018. colorscheme : this.getAttribute('color-scheme'),
  11019. action : this.getAttribute('action'),
  11020. ref : this.getAttribute('ref'),
  11021. show_faces : this._shouldShowFaces(),
  11022. no_resize : this._getBoolAttribute('no_resize'),
  11023. send : this._getBoolAttribute('send'),
  11024. url_map : this.getAttribute('url_map'),
  11025. extended_social_context :
  11026. this._getBoolAttribute('extended_social_context', false)
  11027. };
  11028. this._rootPadding = {
  11029. left: parseFloat(FB.Dom.getStyle(this.dom, 'paddingLeft')),
  11030. top: parseFloat(FB.Dom.getStyle(this.dom, 'paddingTop'))
  11031. };
  11032. return true;
  11033. },
  11034. oneTimeSetup : function() {
  11035. this.subscribe('xd.authPrompted',
  11036. FB.bind(this._onAuthPrompt, this));
  11037. this.subscribe('xd.edgeCreated',
  11038. FB.bind(this._onEdgeCreate, this));
  11039. this.subscribe('xd.edgeRemoved',
  11040. FB.bind(this._onEdgeRemove, this));
  11041. this.subscribe('xd.presentEdgeCommentDialog',
  11042. FB.bind(this._handleEdgeCommentDialogPresentation, this));
  11043. this.subscribe('xd.dismissEdgeCommentDialog',
  11044. FB.bind(this._handleEdgeCommentDialogDismissal, this));
  11045. this.subscribe('xd.hideEdgeCommentDialog',
  11046. FB.bind(this._handleEdgeCommentDialogHide, this));
  11047. this.subscribe('xd.showEdgeCommentDialog',
  11048. FB.bind(this._handleEdgeCommentDialogShow, this));
  11049. },
  11050. /**
  11051. * Get the initial size.
  11052. *
  11053. * @return {Object} the size
  11054. */
  11055. getSize: function() {
  11056. return {
  11057. width: this._getWidgetWidth(),
  11058. height: this._getWidgetHeight()
  11059. };
  11060. },
  11061. /**
  11062. * Returns the height of the widget iframe, taking into
  11063. * account the chosen layout, a user-supplied height, and
  11064. * the min and max values we'll allow. As it turns out, we
  11065. * don't see too much. (At the moment, we ignore the any
  11066. * user-defined height, but that might change.)
  11067. *
  11068. * This logic is replicated in html/plugins/like.php and
  11069. * lib/external_node/param_validation.php, and must be replicated
  11070. * because it helps size the client's iframe.
  11071. *
  11072. * @return {String} the CSS-legitimate width in pixels, as
  11073. * with '460px'.
  11074. */
  11075. _getWidgetHeight : function() {
  11076. var layout = this._getLayout();
  11077. var should_show_faces = this._shouldShowFaces() ? 'show' : 'hide';
  11078. var send = this._getBoolAttribute('send');
  11079. var box_count = 65 + (send ? 25 : 0);
  11080. var layoutToDefaultHeightMap =
  11081. { 'standard' : {'show': 80, 'hide': 35},
  11082. 'box_count' : {'show': box_count, 'hide': box_count},
  11083. 'button_count' : {'show': 21, 'hide': 21},
  11084. 'simple' : {'show': 20, 'hide': 20}};
  11085. return layoutToDefaultHeightMap[layout][should_show_faces];
  11086. },
  11087. /**
  11088. * Returns the width of the widget iframe, taking into
  11089. * account the chosen layout, the user supplied width, and
  11090. * the min and max values we'll allow. There is much more
  11091. * flexibility in how wide the widget is, so a user-supplied
  11092. * width just needs to fall within a certain range.
  11093. *
  11094. * This logic is replicated in html/plugins/like.php and
  11095. * lib/external_node/param_validation.php, and must be replicated
  11096. * because it helps size the client's iframe.
  11097. *
  11098. * @return {String} the CSS-legitimate width in pixels, as
  11099. * with '460px'.
  11100. */
  11101. _getWidgetWidth : function() {
  11102. var layout = this._getLayout();
  11103. var send = this._getBoolAttribute('send');
  11104. var should_show_faces = this._shouldShowFaces() ? 'show' : 'hide';
  11105. var recommend = (this.getAttribute('action') === 'recommend');
  11106. var standard_min_width =
  11107. (recommend ? 265 : 225) + (send ? 60 : 0);
  11108. var button_count_default_width =
  11109. (recommend ? 130 : 90) + (send ? 60 : 0);
  11110. var box_count_default_width =
  11111. this.getAttribute('action') === 'recommend' ? 100 : 55;
  11112. var simple_default_width =
  11113. this.getAttribute('action') === 'recommend' ? 90 : 50;
  11114. var layoutToDefaultWidthMap =
  11115. { 'standard': {'show': 450,
  11116. 'hide': 450},
  11117. 'box_count': {'show': box_count_default_width,
  11118. 'hide': box_count_default_width},
  11119. 'button_count': {'show': button_count_default_width,
  11120. 'hide': button_count_default_width},
  11121. 'simple': {'show': simple_default_width,
  11122. 'hide': simple_default_width}};
  11123. var defaultWidth = layoutToDefaultWidthMap[layout][should_show_faces];
  11124. var width = this._getPxAttribute('width', defaultWidth);
  11125. var allowedWidths =
  11126. { 'standard' : {'min' : standard_min_width, 'max' : 900},
  11127. 'box_count' : {'min' : box_count_default_width,
  11128. 'max' : 900},
  11129. 'button_count' : {'min' : button_count_default_width,
  11130. 'max' : 900},
  11131. 'simple' : {'min' : 49,
  11132. 'max' : 900}};
  11133. if (width < allowedWidths[layout].min) {
  11134. width = allowedWidths[layout].min;
  11135. } else if (width > allowedWidths[layout].max) {
  11136. width = allowedWidths[layout].max;
  11137. }
  11138. return width;
  11139. },
  11140. /**
  11141. * Returns the layout provided by the user, which can be
  11142. * any one of 'standard', 'box', or 'bar'. If the user
  11143. * omits a layout, or if they layout they specify is invalid,
  11144. * then we just go with 'standard'.
  11145. *
  11146. * This logic is replicated in html/plugins/like.php and
  11147. * lib/external_node/param_validation.php, and must be replicated
  11148. * because it helps size the client's iframe.
  11149. *
  11150. * @return {String} the layout of the Connect Widget.
  11151. */
  11152. _getLayout : function() {
  11153. return this._getAttributeFromList(
  11154. 'layout',
  11155. 'standard',
  11156. ['standard', 'button_count', 'box_count', 'simple']);
  11157. },
  11158. /**
  11159. * Returns true if and only if we should be showing faces in the
  11160. * widget, and false otherwise.
  11161. *
  11162. * This logic is replicated in html/plugins/like.php and
  11163. * lib/external_node/param_validation.php, and must be replicated
  11164. * because it helps size the client's iframe.
  11165. *
  11166. * @return {String} described above.
  11167. */
  11168. _shouldShowFaces : function() {
  11169. return this._getLayout() === 'standard' &&
  11170. this._getBoolAttribute('show-faces', true);
  11171. },
  11172. /**
  11173. * Handles the event fired when the user actually connects to
  11174. * something. The idea is to tell the host to drop in
  11175. * another iframe widget--an FB.XFBML.EdgeCommentWidget--
  11176. * and sensibly position it so it partially overlays
  11177. * the mother widget.
  11178. *
  11179. * @param {Object} message a dictionary of information about the
  11180. * event.
  11181. * @return void
  11182. */
  11183. _handleEdgeCommentDialogPresentation : function(message) {
  11184. if (!this.isValid()) {
  11185. return;
  11186. }
  11187. var comment_node = document.createElement('span');
  11188. this._commentSlave = this._createEdgeCommentWidget(message, comment_node);
  11189. this.dom.appendChild(comment_node);
  11190. this._commentSlave.process();
  11191. this._commentWidgetNode = comment_node;
  11192. },
  11193. /**
  11194. * Given a message from an xd comm event and the node where the edge comment
  11195. * widget is to live in the dom, create an instance of
  11196. * FB.XFBML.EdgeCommentWidget with the default parameters for an edge
  11197. * widget. The idea is to allow this method to be overridden so that
  11198. * other edge widgets can have different parameters and maybe even other
  11199. * (sub)types of FB.XFBML.EdgeCommentWidget (e.g. SendButtonFormWidget).
  11200. *
  11201. * @param {Object} message a dictionary of information about the xd comm event
  11202. * @param {Object} comment_node the dom node where the edgecommentwidget
  11203. * will live.
  11204. * @return FB.XFBML.EdgeCommentWidget
  11205. */
  11206. _createEdgeCommentWidget : function(message, comment_node) {
  11207. var opts = {
  11208. commentNode : comment_node,
  11209. externalUrl : message.externalURL,
  11210. masterFrameName : message.masterFrameName,
  11211. layout : this._getLayout(),
  11212. relativeHeightOffset : this._getHeightOffset(message),
  11213. relativeWidthOffset : this._getWidthOffset(message),
  11214. anchorTargetX : parseFloat(message['query[anchorTargetX]']) +
  11215. this._rootPadding.left,
  11216. anchorTargetY : parseFloat(message['query[anchorTargetY]']) +
  11217. this._rootPadding.top,
  11218. width : parseFloat(message.width),
  11219. height : parseFloat(message.height),
  11220. paddingLeft : this._rootPadding.left
  11221. };
  11222. return new FB.XFBML.EdgeCommentWidget(opts);
  11223. },
  11224. /**
  11225. * Determines the relative height offset of the external comment
  11226. * widget relative to the top of the primary like widget.
  11227. * The numbers can potentially vary from layout to layout, because
  11228. * the comment widget anchors on varying elements (button itself
  11229. * in button_count and standard, the favicon in box_count, etc.)
  11230. *
  11231. * @param {Object} message the full message of information passed from
  11232. * like plugin to the comment widget.
  11233. * @return {Number} relative offset that should influence
  11234. * placement of the external comment widget vsv the primary
  11235. * comment widget.
  11236. */
  11237. _getHeightOffset : function(message) {
  11238. return parseFloat(message['anchorGeometry[y]']) +
  11239. parseFloat(message['anchorPosition[y]']) +
  11240. this._rootPadding.top;
  11241. },
  11242. /**
  11243. * Determines the relative offset of the external comment
  11244. * widget relative to the left (or right for RTL locales)
  11245. * of the primary like widget.
  11246. *
  11247. * @param {Object} message the full message of information passed from
  11248. * like plugin to the comment widget.
  11249. * @return {Number} relative offset than should influence
  11250. * placement of the external comment widget vsv the primary
  11251. * comment widget.
  11252. */
  11253. _getWidthOffset : function(message) {
  11254. var off = parseFloat(message['anchorPosition[x]']) + this._rootPadding.left;
  11255. var plugin_left = FB.Dom.getPosition(this.dom).x;
  11256. var plugin_width = this.dom.offsetWidth;
  11257. var screen_width = FB.Dom.getViewportInfo().width;
  11258. var comment_width = parseFloat(message.width);
  11259. var flipit = false;
  11260. if (FB._localeIsRtl) {
  11261. flipit = comment_width < plugin_left;
  11262. } else if ((plugin_left + comment_width) > screen_width) {
  11263. flipit = true;
  11264. }
  11265. if (flipit) {
  11266. off += parseFloat(message['anchorGeometry[x]']) - comment_width;
  11267. }
  11268. return off;
  11269. },
  11270. /**
  11271. * Returns an object of options that is used in several types of edge comment
  11272. * widget
  11273. *
  11274. * @param {Object} message a dictionary of information about the xd comm event
  11275. * @param {Object} comment_node the dom node where the edgecommentwidget
  11276. * will live
  11277. * @return {Object} options
  11278. */
  11279. _getCommonEdgeCommentWidgetOpts : function(message,
  11280. comment_node) {
  11281. return {
  11282. colorscheme : this._attr.colorscheme,
  11283. commentNode : comment_node,
  11284. controllerID : message.controllerID,
  11285. nodeImageURL : message.nodeImageURL,
  11286. nodeRef : this._attr.ref,
  11287. nodeTitle : message.nodeTitle,
  11288. nodeURL : message.nodeURL,
  11289. nodeSummary : message.nodeSummary,
  11290. width : parseFloat(message.width),
  11291. height : parseFloat(message.height),
  11292. relativeHeightOffset : this._getHeightOffset(message),
  11293. relativeWidthOffset : this._getWidthOffset(message),
  11294. error : message.error,
  11295. siderender : message.siderender,
  11296. extended_social_context : message.extended_social_context,
  11297. anchorTargetX : parseFloat(message['query[anchorTargetX]']) +
  11298. this._rootPadding.left,
  11299. anchorTargetY : parseFloat(message['query[anchorTargetY]']) +
  11300. this._rootPadding.top
  11301. };
  11302. },
  11303. /**
  11304. * Handles the XD event instructing the host to
  11305. * remove the comment widget iframe. The DOM node
  11306. * for this widget is currently carrying just one child
  11307. * node, which is the span representing the iframe.
  11308. * We just need to return that one child in order for the
  11309. * comment widget to disappear.
  11310. *
  11311. * @param {Object} message a dictionary of information about
  11312. * the event.
  11313. * @return void
  11314. */
  11315. _handleEdgeCommentDialogDismissal : function(message) {
  11316. if (this._commentWidgetNode) {
  11317. this.dom.removeChild(this._commentWidgetNode);
  11318. delete this._commentWidgetNode;
  11319. }
  11320. },
  11321. /**
  11322. * Handles the XD event instructing the hose to hide the comment
  11323. * widget iframe.
  11324. */
  11325. _handleEdgeCommentDialogHide: function() {
  11326. if (this._commentWidgetNode) {
  11327. this._commentWidgetNode.style.display="none";
  11328. }
  11329. },
  11330. /**
  11331. * Handles the XD event instructing the hose to show the comment
  11332. * widget iframe.
  11333. */
  11334. _handleEdgeCommentDialogShow: function() {
  11335. if (this._commentWidgetNode) {
  11336. this._commentWidgetNode.style.display="block";
  11337. }
  11338. },
  11339. /**
  11340. * Helper method that fires a specified event and also invokes the
  11341. * given inline handler.
  11342. */
  11343. _fireEventAndInvokeHandler: function(eventName, eventAttribute) {
  11344. FB.Helper.fireEvent(eventName, this);
  11345. FB.Helper.invokeHandler(
  11346. this.getAttribute(eventAttribute), this, [this._attr.href]); // inline
  11347. },
  11348. /**
  11349. * Invoked when the user likes/recommends/whatever the thing to create an
  11350. * edge.
  11351. */
  11352. _onEdgeCreate: function() {
  11353. this._fireEventAndInvokeHandler('edge.create', 'on-create');
  11354. },
  11355. /**
  11356. * Invoked when the user removes a like/recommendation/etc association with
  11357. * something.
  11358. */
  11359. _onEdgeRemove: function() {
  11360. this._fireEventAndInvokeHandler('edge.remove', 'on-remove');
  11361. },
  11362. /**
  11363. * Invoked when the user is prompted to opt-in/log-in due to clicking
  11364. * on an edge widget in an un-authed state.
  11365. */
  11366. _onAuthPrompt: function() {
  11367. this._fireEventAndInvokeHandler('auth.prompt', 'on-prompt');
  11368. }
  11369. });
  11370. /**
  11371. * @provides fb.xfbml.sendbuttonformwidget
  11372. * @requires fb.type
  11373. * fb.xfbml.edgecommentwidget
  11374. * @css fb.css.sendbuttonformwidget
  11375. * @layer xfbml
  11376. */
  11377. /**
  11378. * Copyright Facebook Inc.
  11379. *
  11380. * Licensed under the Apache License, Version 2.0 (the "License");
  11381. * you may not use this file except in compliance with the License.
  11382. * You may obtain a copy of the License at
  11383. *
  11384. * http://www.apache.org/licenses/LICENSE-2.0
  11385. *
  11386. * Unless required by applicable law or agreed to in writing, software
  11387. * distributed under the License is distributed on an "AS IS" BASIS,
  11388. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11389. * See the License for the specific language governing permissions and
  11390. * limitations under the License.
  11391. */
  11392. /**
  11393. * Implementation for the send button form widget,
  11394. * which is an edge comment widget.
  11395. *
  11396. * @class FB.XFBML.SendButtonFormWidget
  11397. * @extends FB.XFBML.EdgeCommentWidget
  11398. * @private
  11399. */
  11400. FB.subclass('XFBML.SendButtonFormWidget', 'XFBML.EdgeCommentWidget',
  11401. function(opts) {
  11402. this._base(opts);
  11403. FB.Dom.addCss(this.dom, 'fb_send_button_form_widget');
  11404. FB.Dom.addCss(this.dom, opts.colorscheme);
  11405. FB.Dom.addCss(this.dom,
  11406. (typeof opts.siderender != 'undefined' && opts.siderender) ?
  11407. 'siderender' : '');
  11408. // The url title, and image URL of the node
  11409. this._attr.nodeImageURL = opts.nodeImageURL;
  11410. this._attr.nodeRef = opts.nodeRef;
  11411. this._attr.nodeTitle = opts.nodeTitle;
  11412. this._attr.nodeURL = opts.nodeURL;
  11413. this._attr.nodeSummary = opts.nodeSummary;
  11414. this._attr.offsetX = opts.relativeWidthOffset;
  11415. this._attr.offsetY = opts.relativeHeightOffset;
  11416. this._attr.anchorTargetX = opts.anchorTargetX;
  11417. this._attr.anchorTargetY = opts.anchorTargetY;
  11418. // Main channel
  11419. this._attr.channel = this.getChannelUrl();
  11420. // We use the controller ID of the SendButton js controller
  11421. // in order for the form controller to inform (via cross-frame Arbiter)
  11422. // the SendButton js controller of events in the form (e.g. message sent).
  11423. this._attr.controllerID = opts.controllerID;
  11424. // Light or dark color scheme.
  11425. this._attr.colorscheme = opts.colorscheme;
  11426. // If there was any error retrieving the node
  11427. this._attr.error = opts.error;
  11428. // whether the flyout to appear to come out from the side or from above
  11429. this._attr.siderender = opts.siderender;
  11430. // Determine if we should show extended social context on the widget
  11431. this._attr.extended_social_context = opts.extended_social_context;
  11432. }, {
  11433. // Since this comment widget is rendered on its own
  11434. // instead of having html injected into it,
  11435. // there will be a very small delay. So in meantime, let's show a loader
  11436. _showLoader: true,
  11437. getUrlBits: function() {
  11438. return { name: 'send_button_form_shell', params: this._attr };
  11439. },
  11440. oneTimeSetup: function() {
  11441. this.subscribe('xd.messageSent',
  11442. FB.bind(this._onMessageSent, this));
  11443. },
  11444. _onMessageSent: function() {
  11445. FB.Event.fire('message.send', this._attr.nodeURL, this);
  11446. }
  11447. });
  11448. /**
  11449. * Copyright Facebook Inc.
  11450. *
  11451. * Licensed under the Apache License, Version 2.0 (the "License");
  11452. * you may not use this file except in compliance with the License.
  11453. * You may obtain a copy of the License at
  11454. *
  11455. * http://www.apache.org/licenses/LICENSE-2.0
  11456. *
  11457. * Unless required by applicable law or agreed to in writing, software
  11458. * distributed under the License is distributed on an "AS IS" BASIS,
  11459. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11460. * See the License for the specific language governing permissions and
  11461. * limitations under the License.
  11462. *
  11463. * @provides fb.xfbml.send
  11464. * @layer xfbml
  11465. * @requires fb.type
  11466. * fb.xfbml.edgewidget
  11467. * fb.xfbml.sendbuttonformwidget
  11468. */
  11469. /**
  11470. * Implementation for the fb:send tag.
  11471. *
  11472. * @class FB.XFBML.Send
  11473. * @extends FB.XFBML.EdgeWidget
  11474. * @private
  11475. */
  11476. FB.subclass('XFBML.Send', 'XFBML.EdgeWidget', null, {
  11477. /**
  11478. * Do initial attribute processing.
  11479. */
  11480. setupAndValidate: function() {
  11481. FB.Dom.addCss(this.dom, 'fb_edge_widget_with_comment');
  11482. this._attr = {
  11483. channel : this.getChannelUrl(),
  11484. api_key : FB._apiKey,
  11485. font : this.getAttribute('font'),
  11486. colorscheme : this.getAttribute('colorscheme', 'light'),
  11487. href : this.getAttribute('href', window.location.href),
  11488. ref : this.getAttribute('ref'),
  11489. extended_social_context :
  11490. this.getAttribute('extended_social_context', false)
  11491. };
  11492. this._rootPadding = {
  11493. left: parseFloat(FB.Dom.getStyle(this.dom, 'paddingLeft')),
  11494. top: parseFloat(FB.Dom.getStyle(this.dom, 'paddingTop'))
  11495. };
  11496. return true;
  11497. },
  11498. /**
  11499. * Get the URL bits for the iframe.
  11500. *
  11501. * @return {Object} the iframe URL bits
  11502. */
  11503. getUrlBits: function() {
  11504. return { name: 'send', params: this._attr };
  11505. },
  11506. /**
  11507. * Given a message from an xd comm event and the node where the edge comment
  11508. * widget is to live in the dom, create an instance of
  11509. * FB.XFBML.SendButtonFormWidget to be displayed right below the send button.
  11510. *
  11511. * @param {Object} message a dictionary of information about the xd comm event
  11512. * @param {Object} comment_node the dom node where the edgecommentwidget
  11513. * will live.
  11514. * @return FB.XFBML.EdgeCommentWidget
  11515. */
  11516. _createEdgeCommentWidget: function(message, comment_node) {
  11517. var opts = this._getCommonEdgeCommentWidgetOpts(message, comment_node);
  11518. return new FB.XFBML.SendButtonFormWidget(opts);
  11519. },
  11520. /**
  11521. * Get the initial size.
  11522. *
  11523. * @return {Object} the size
  11524. */
  11525. getSize: function() {
  11526. return {
  11527. width : FB.XFBML.Send.Dimensions.width,
  11528. height : FB.XFBML.Send.Dimensions.height
  11529. };
  11530. }
  11531. });
  11532. FB.provide('XFBML.Send', {
  11533. Dimensions: {
  11534. width: 80,
  11535. height: 25
  11536. }
  11537. });
  11538. /**
  11539. * Copyright Facebook Inc.
  11540. *
  11541. * Licensed under the Apache License, Version 2.0 (the "License");
  11542. * you may not use this file except in compliance with the License.
  11543. * You may obtain a copy of the License at
  11544. *
  11545. * http://www.apache.org/licenses/LICENSE-2.0
  11546. *
  11547. * Unless required by applicable law or agreed to in writing, software
  11548. * distributed under the License is distributed on an "AS IS" BASIS,
  11549. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11550. * See the License for the specific language governing permissions and
  11551. * limitations under the License.
  11552. *
  11553. * @provides fb.xfbml.like
  11554. * @layer xfbml
  11555. * @requires fb.type
  11556. * fb.xfbml.edgewidget
  11557. * fb.xfbml.send
  11558. * fb.intl
  11559. */
  11560. /**
  11561. * Implementation for fb:like tag.
  11562. *
  11563. * @class FB.XFBML.Like
  11564. * @extends FB.XFBML.EdgeWidget
  11565. * @private
  11566. */
  11567. FB.subclass('XFBML.Like', 'XFBML.EdgeWidget', null, {
  11568. /**
  11569. * Like Plugin isn't actually widget-pipe enabled yet, though
  11570. * in principle it can be by just switching this false to
  11571. * true.
  11572. */
  11573. _widgetPipeEnabled: true,
  11574. /**
  11575. * Get the URL bits for the iframe.
  11576. *
  11577. * @return {Object} the iframe URL bits
  11578. */
  11579. getUrlBits: function() {
  11580. return { name: 'like', params: this._attr };
  11581. },
  11582. /**
  11583. * Given a message from an xd comm event and the node where the edge comment
  11584. * widget is to live in the dom, create an instance of
  11585. * FB.XFBML.EdgeCommentWidget to be displayed right below the button.
  11586. *
  11587. * @param {Object} message a dictionary of information about the xd comm event
  11588. * @param {Object} comment_node the dom node where the edgecommentwidget
  11589. * will live.
  11590. * @return FB.XFBML.EdgeCommentWidget
  11591. */
  11592. _createEdgeCommentWidget: function(message, comment_node) {
  11593. // the like widget is also responsible for the send button form if the user
  11594. // decides to put a send button with the like widget (e.g. <fb:like
  11595. // send="true"></fb:like>)
  11596. if ('send' in this._attr && 'widget_type' in message &&
  11597. message.widget_type == 'send') {
  11598. var opts = this._getCommonEdgeCommentWidgetOpts(message,
  11599. comment_node);
  11600. return new FB.XFBML.SendButtonFormWidget(opts);
  11601. } else {
  11602. return this._callBase("_createEdgeCommentWidget",
  11603. message,
  11604. comment_node);
  11605. }
  11606. },
  11607. /**
  11608. * Get the title attribute for the like plugin's iframe.
  11609. *
  11610. * @return {String} the title of the like plugin's iframe.
  11611. */
  11612. getIframeTitle: function() {
  11613. return 'Like this content on Facebook.';
  11614. }
  11615. });
  11616. /**
  11617. * Copyright Facebook Inc.
  11618. *
  11619. * Licensed under the Apache License, Version 2.0 (the "License");
  11620. * you may not use this file except in compliance with the License.
  11621. * You may obtain a copy of the License at
  11622. *
  11623. * http://www.apache.org/licenses/LICENSE-2.0
  11624. *
  11625. * Unless required by applicable law or agreed to in writing, software
  11626. * distributed under the License is distributed on an "AS IS" BASIS,
  11627. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11628. * See the License for the specific language governing permissions and
  11629. * limitations under the License.
  11630. *
  11631. * @provides fb.xfbml.likebox
  11632. * @layer xfbml
  11633. * @requires fb.type fb.xfbml.iframewidget
  11634. */
  11635. /**
  11636. * Implementation for fb:like-box tag.
  11637. *
  11638. * @class FB.XFBML.LikeBox
  11639. * @extends FB.XFBML.IframeWidget
  11640. * @private
  11641. */
  11642. FB.subclass('XFBML.LikeBox', 'XFBML.EdgeWidget', null, {
  11643. _visibleAfter: 'load',
  11644. /**
  11645. * Do initial attribute processing.
  11646. */
  11647. setupAndValidate: function() {
  11648. this._attr = {
  11649. channel : this.getChannelUrl(),
  11650. api_key : FB._apiKey,
  11651. connections : this.getAttribute('connections'),
  11652. css : this.getAttribute('css'),
  11653. height : this.getAttribute('height'),
  11654. id : this.getAttribute('profile-id'),
  11655. header : this._getBoolAttribute('header', true),
  11656. name : this.getAttribute('name'),
  11657. show_faces : this._getBoolAttribute('show-faces', true),
  11658. stream : this._getBoolAttribute('stream', true),
  11659. width : this._getPxAttribute('width', 300),
  11660. href : this.getAttribute('href'),
  11661. colorscheme : this.getAttribute('colorscheme', 'light'),
  11662. border_color: this.getAttribute('border_color')
  11663. };
  11664. if (this._getBoolAttribute('force_wall', false)) {
  11665. this._attr.force_wall = true;
  11666. }
  11667. // also allow connections attr, if specified, to override
  11668. // show_faces
  11669. if (this._attr.connections === '0') {
  11670. this._attr.show_faces = false;
  11671. } else if (this._attr.connections) {
  11672. this._attr.show_faces = true;
  11673. }
  11674. // "id" or "name" or "href" (for Open Graph) is required
  11675. if (!this._attr.id && !this._attr.name && !this._attr.href) {
  11676. FB.log('<fb:like-box> requires one of the "id" or "name" attributes.');
  11677. return false;
  11678. }
  11679. var height = this._attr.height;
  11680. if (!height) {
  11681. if (!this._attr.show_faces &&
  11682. !this._attr.stream) {
  11683. height = 62;
  11684. } else {
  11685. height = 95;
  11686. if (this._attr.show_faces) {
  11687. height += 163;
  11688. }
  11689. if (this._attr.stream) {
  11690. height += 300;
  11691. }
  11692. // add space for header
  11693. if (this._attr.header &&
  11694. this._attr.header !== '0') {
  11695. height += 32;
  11696. }
  11697. }
  11698. }
  11699. this._attr.height = height;
  11700. // listen for the XD 'likeboxLiked' and 'likeboxUnliked' events
  11701. this.subscribe('xd.likeboxLiked', FB.bind(this._onLiked, this));
  11702. this.subscribe('xd.likeboxUnliked', FB.bind(this._onUnliked, this));
  11703. return true;
  11704. },
  11705. /**
  11706. * Get the initial size.
  11707. *
  11708. * @return {Object} the size
  11709. */
  11710. getSize: function() {
  11711. return { width: this._attr.width, height: this._attr.height };
  11712. },
  11713. /**
  11714. * Get the URL bits for the iframe.
  11715. *
  11716. * @return {Object} the iframe URL bits
  11717. */
  11718. getUrlBits: function() {
  11719. return { name: 'likebox', params: this._attr };
  11720. },
  11721. /**
  11722. * Invoked when the user Likes the page via the Like button
  11723. * in the likebox.
  11724. */
  11725. _onLiked: function() {
  11726. FB.Helper.fireEvent('edge.create', this);
  11727. },
  11728. /**
  11729. * Invoked when the user Unlikes the page via the Unlike button
  11730. * in the likebox.
  11731. */
  11732. _onUnliked: function() {
  11733. FB.Helper.fireEvent('edge.remove', this);
  11734. }
  11735. });
  11736. /**
  11737. * Copyright Facebook Inc.
  11738. *
  11739. * Licensed under the Apache License, Version 2.0 (the "License");
  11740. * you may not use this file except in compliance with the License.
  11741. * You may obtain a copy of the License at
  11742. *
  11743. * http://www.apache.org/licenses/LICENSE-2.0
  11744. *
  11745. * Unless required by applicable law or agreed to in writing, software
  11746. * distributed under the License is distributed on an "AS IS" BASIS,
  11747. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11748. * See the License for the specific language governing permissions and
  11749. * limitations under the License.
  11750. *
  11751. * @provides fb.xfbml.livestream
  11752. * @layer xfbml
  11753. * @requires fb.type fb.xfbml.iframewidget
  11754. */
  11755. /**
  11756. * Implementation for fb:live-stream tag.
  11757. *
  11758. * @class FB.XFBML.LiveStream
  11759. * @extends FB.XFBML.IframeWidget
  11760. * @private
  11761. */
  11762. FB.subclass('XFBML.LiveStream', 'XFBML.IframeWidget', null, {
  11763. _visibleAfter: 'load',
  11764. /**
  11765. * Do initial attribute processing.
  11766. */
  11767. setupAndValidate: function() {
  11768. this._attr = {
  11769. app_id : this.getAttribute('event-app-id'),
  11770. height : this._getPxAttribute('height', 500),
  11771. hideFriendsTab : this.getAttribute('hide-friends-tab'),
  11772. redesigned : this._getBoolAttribute('redesigned-stream'),
  11773. width : this._getPxAttribute('width', 400),
  11774. xid : this.getAttribute('xid', 'default'),
  11775. always_post_to_friends : this._getBoolAttribute('always-post-to-friends'),
  11776. via_url : this.getAttribute('via_url')
  11777. };
  11778. return true;
  11779. },
  11780. /**
  11781. * Get the initial size.
  11782. *
  11783. * @return {Object} the size
  11784. */
  11785. getSize: function() {
  11786. return { width: this._attr.width, height: this._attr.height };
  11787. },
  11788. /**
  11789. * Get the URL bits for the iframe.
  11790. *
  11791. * @return {Object} the iframe URL bits
  11792. */
  11793. getUrlBits: function() {
  11794. var name = this._attr.redesigned ? 'live_stream_box' : 'livefeed';
  11795. if (this._getBoolAttribute('modern', false)) {
  11796. name = 'live_stream';
  11797. }
  11798. return { name: name, params: this._attr };
  11799. }
  11800. });
  11801. /**
  11802. * Copyright Facebook Inc.
  11803. *
  11804. * Licensed under the Apache License, Version 2.0 (the "License");
  11805. * you may not use this file except in compliance with the License.
  11806. * You may obtain a copy of the License at
  11807. *
  11808. * http://www.apache.org/licenses/LICENSE-2.0
  11809. *
  11810. * Unless required by applicable law or agreed to in writing, software
  11811. * distributed under the License is distributed on an "AS IS" BASIS,
  11812. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11813. * See the License for the specific language governing permissions and
  11814. * limitations under the License.
  11815. *
  11816. * @provides fb.xfbml.login
  11817. * @layer xfbml
  11818. * @requires fb.type fb.xfbml.iframewidget fb.xfbml.facepile fb.auth
  11819. */
  11820. /**
  11821. * Implementation for fb:login tag.
  11822. *
  11823. * @class FB.XFBML.Login
  11824. * @extends FB.XFBML.IframeWidget
  11825. * @private
  11826. */
  11827. FB.subclass('XFBML.Login', 'XFBML.Facepile', null, {
  11828. _visibleAfter: 'load',
  11829. /**
  11830. * Get the initial size.
  11831. *
  11832. * By default, shows one row of 6 profiles
  11833. *
  11834. * @return {Object} the size
  11835. */
  11836. getSize: function() {
  11837. return { width: this._attr.width, height: 94 };
  11838. },
  11839. /**
  11840. * Get the URL bits for the iframe.
  11841. *
  11842. * @return {Object} the iframe URL bits
  11843. */
  11844. getUrlBits: function() {
  11845. return { name: 'login', params: this._attr };
  11846. }
  11847. });
  11848. /**
  11849. * Copyright Facebook Inc.
  11850. *
  11851. * Licensed under the Apache License, Version 2.0 (the "License");
  11852. * you may not use this file except in compliance with the License.
  11853. * You may obtain a copy of the License at
  11854. *
  11855. * http://www.apache.org/licenses/LICENSE-2.0
  11856. *
  11857. * Unless required by applicable law or agreed to in writing, software
  11858. * distributed under the License is distributed on an "AS IS" BASIS,
  11859. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11860. * See the License for the specific language governing permissions and
  11861. * limitations under the License.
  11862. *
  11863. * @provides fb.xfbml.loginbutton
  11864. * @layer xfbml
  11865. * @requires fb.type
  11866. * fb.intl
  11867. * fb.xfbml.buttonelement
  11868. * fb.helper
  11869. * fb.auth
  11870. */
  11871. /**
  11872. * Implementation for fb:login-button tag.
  11873. *
  11874. * @class FB.XFBML.LoginButton
  11875. * @extends FB.XFBML.ButtonElement
  11876. * @private
  11877. */
  11878. FB.subclass('XFBML.LoginButton', 'XFBML.ButtonElement', null, {
  11879. /**
  11880. * Do initial attribute processing.
  11881. *
  11882. * @return {Boolean} true to continue processing, false to halt it
  11883. */
  11884. setupAndValidate: function() {
  11885. // This method sometimes makes a callback that will call process() which
  11886. // in-tern calls this method. Let's only setup once.
  11887. if (this._alreadySetup) {
  11888. return true;
  11889. }
  11890. this._alreadySetup = true;
  11891. this._attr = {
  11892. autologoutlink : this._getBoolAttribute('auto-logout-link'),
  11893. length : this._getAttributeFromList(
  11894. 'length', // name
  11895. 'short', // defaultValue
  11896. ['long', 'short'] // allowed
  11897. ),
  11898. onlogin : this.getAttribute('on-login'),
  11899. perms : this.getAttribute('perms'),
  11900. scope : this.getAttribute('scope'),
  11901. registration_url : this.getAttribute('registration-url'),
  11902. status : 'unknown'
  11903. };
  11904. // Change the displayed HTML whenever we know more about the auth status
  11905. if (this._attr.autologoutlink) {
  11906. FB.Event.subscribe('auth.statusChange', FB.bind(this.process, this));
  11907. }
  11908. if (this._attr.registration_url) {
  11909. // The act of putting a registration-url means that unTOSed users will
  11910. // be redirected to the registration widget instead of the TOS dialog.
  11911. FB.Event.subscribe('auth.statusChange',
  11912. this._saveStatus(this.process, /* on_click */ false));
  11913. // Change the displayed HTML immediately
  11914. FB.getLoginStatus(this._saveStatus(this.process, /* on_click */ false));
  11915. }
  11916. return true;
  11917. },
  11918. /**
  11919. * Should return the button markup. The default behaviour is to return the
  11920. * original innerHTML of the element.
  11921. *
  11922. * @return {String} the HTML markup for the button
  11923. */
  11924. getButtonMarkup: function() {
  11925. var originalHTML = this.getOriginalHTML();
  11926. if (originalHTML) {
  11927. return originalHTML;
  11928. }
  11929. if (!this._attr.registration_url) {
  11930. // In the normal sense, it says "Logout" or "Login"
  11931. if (FB.getAccessToken() && this._attr.autologoutlink) {
  11932. return FB.Intl.tx._("Facebook Logout");
  11933. } else if (FB.getAccessToken()) {
  11934. // logged in already, don't render anything
  11935. return '';
  11936. } else {
  11937. return this._getLoginText();
  11938. }
  11939. } else {
  11940. // If there is a registration url it says "Logout", "Register" or "Login"
  11941. switch (this._attr.status) {
  11942. case 'unknown':
  11943. return this._getLoginText();
  11944. case 'notConnected':
  11945. case 'not_authorized':
  11946. return FB.Intl.tx._("Register");
  11947. case 'connected':
  11948. if (FB.getAccessToken() && this._attr.autologoutlink) {
  11949. return FB.Intl.tx._("Facebook Logout");
  11950. }
  11951. // registered already, don't render anything
  11952. return '';
  11953. default:
  11954. FB.log('Unknown status: ' + this._attr.status);
  11955. return FB.Intl.tx._("Log In");
  11956. }
  11957. }
  11958. },
  11959. /**
  11960. * Helper function to show "Login" or "Login with Facebook"
  11961. */
  11962. _getLoginText: function() {
  11963. return this._attr.length == 'short'
  11964. ? FB.Intl.tx._("Log In")
  11965. : FB.Intl.tx._("Log In with Facebook");
  11966. },
  11967. /**
  11968. * The ButtonElement base class will invoke this when the button is clicked.
  11969. */
  11970. onClick: function() {
  11971. if (!this._attr.registration_url) {
  11972. // In the normal case, this will either log you in, or log you out
  11973. if (!FB.getAccessToken() || !this._attr.autologoutlink) {
  11974. FB.login(FB.bind(this._authCallback, this), {
  11975. perms: this._attr.perms,
  11976. scope: this._attr.scope
  11977. });
  11978. } else {
  11979. FB.logout(FB.bind(this._authCallback, this));
  11980. }
  11981. } else {
  11982. // If there is a registration url, first log them into Facebook
  11983. // and then send them to the registration url
  11984. switch (this._attr.status) {
  11985. case 'unknown':
  11986. FB.ui({ method: 'auth.logintoFacebook' },
  11987. FB.bind(function(response) {
  11988. // Fetch the status again and then redo the click
  11989. FB.bind(FB.getLoginStatus(
  11990. this._saveStatus(this._authCallback, /* on_click */ true),
  11991. /* force */ true), this);
  11992. }, this)
  11993. );
  11994. break;
  11995. case 'notConnected':
  11996. case 'not_authorized':
  11997. window.top.location = this._attr.registration_url;
  11998. break;
  11999. case 'connected':
  12000. if (!FB.getAccessToken() || !this._attr.autologoutlink) {
  12001. // do nothing except call their callback
  12002. this._authCallback();
  12003. } else {
  12004. FB.logout(FB.bind(this._authCallback, this));
  12005. }
  12006. break;
  12007. default:
  12008. FB.log('Unknown status: ' + this._attr.status);
  12009. }
  12010. }
  12011. },
  12012. /**
  12013. * This will be invoked with the result of the FB.login() or FB.logout() to
  12014. * pass the result to the developer specified callback if any.
  12015. *
  12016. * @param response {Object} the auth response object
  12017. */
  12018. _authCallback: function(response) {
  12019. FB.Helper.invokeHandler(this._attr.onlogin, this, [response]);
  12020. },
  12021. /**
  12022. * A shortcut to save the response status and handle FB.bind().
  12023. *
  12024. * @param cb {Function} callback
  12025. * @param on_click {bool} whether this is an on click event
  12026. */
  12027. _saveStatus: function(cb, on_click) {
  12028. return FB.bind(function(response) {
  12029. if (on_click &&
  12030. this._attr.registration_url &&
  12031. (this._attr.status == 'notConnected' ||
  12032. this._attr.status == 'not_authorized') &&
  12033. (response.status == 'notConnected' ||
  12034. response.status == 'not_authorized')) {
  12035. // user clicked login and is now logged-in but not registered,
  12036. // so redirect to registration uri
  12037. window.top.location = this._attr.registration_url;
  12038. }
  12039. this._attr.status = response.status;
  12040. if (cb) {
  12041. cb = this.bind(cb, this);
  12042. return cb(response);
  12043. }
  12044. }, this);
  12045. }
  12046. });
  12047. /**
  12048. * Copyright Facebook Inc.
  12049. *
  12050. * Licensed under the Apache License, Version 2.0 (the "License");
  12051. * you may not use this file except in compliance with the License.
  12052. * You may obtain a copy of the License at
  12053. *
  12054. * http://www.apache.org/licenses/LICENSE-2.0
  12055. *
  12056. * Unless required by applicable law or agreed to in writing, software
  12057. * distributed under the License is distributed on an "AS IS" BASIS,
  12058. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12059. * See the License for the specific language governing permissions and
  12060. * limitations under the License.
  12061. *
  12062. * @provides fb.xfbml.name
  12063. * @layer xfbml
  12064. * @requires fb.type
  12065. * fb.xfbml
  12066. * fb.dom
  12067. * fb.xfbml.element
  12068. * fb.data
  12069. * fb.helper
  12070. * fb.string
  12071. */
  12072. /**
  12073. * @class FB.XFBML.Name
  12074. * @extends FB.XFBML.Element
  12075. * @private
  12076. */
  12077. FB.subclass('XFBML.Name', 'XFBML.Element', null, {
  12078. /**
  12079. * Processes this tag.
  12080. */
  12081. process: function() {
  12082. FB.copy(this, {
  12083. _uid : this.getAttribute('uid'),
  12084. _firstnameonly : this._getBoolAttribute('first-name-only'),
  12085. _lastnameonly : this._getBoolAttribute('last-name-only'),
  12086. _possessive : this._getBoolAttribute('possessive'),
  12087. _reflexive : this._getBoolAttribute('reflexive'),
  12088. _objective : this._getBoolAttribute('objective'),
  12089. _linked : this._getBoolAttribute('linked', true),
  12090. _subjectId : this.getAttribute('subject-id')
  12091. });
  12092. if (!this._uid) {
  12093. FB.log('"uid" is a required attribute for <fb:name>');
  12094. this.fire('render');
  12095. return;
  12096. }
  12097. var fields = [];
  12098. if (this._firstnameonly) {
  12099. fields.push('first_name');
  12100. } else if (this._lastnameonly) {
  12101. fields.push('last_name');
  12102. } else {
  12103. fields.push('name');
  12104. }
  12105. if (this._subjectId) {
  12106. fields.push('sex');
  12107. if (this._subjectId == FB.Helper.getLoggedInUser()) {
  12108. this._reflexive = true;
  12109. }
  12110. }
  12111. var data;
  12112. // Wait for status to be known
  12113. FB.Event.monitor('auth.statusChange', this.bind(function() {
  12114. // Is Element still in DOM tree?
  12115. if (!this.isValid()) {
  12116. this.fire('render');
  12117. return true; // Stop processing
  12118. }
  12119. if (!this._uid || this._uid == 'loggedinuser') {
  12120. this._uid = FB.Helper.getLoggedInUser();
  12121. }
  12122. if (!this._uid) {
  12123. return; // dont do anything yet
  12124. }
  12125. if (FB.Helper.isUser(this._uid)) {
  12126. data = FB.Data._selectByIndex(fields, 'user', 'uid', this._uid);
  12127. } else {
  12128. data = FB.Data._selectByIndex(['name', 'id'], 'profile', 'id',
  12129. this._uid);
  12130. }
  12131. data.wait(this.bind(function(data) {
  12132. if (this._subjectId == this._uid) {
  12133. this._renderPronoun(data[0]);
  12134. } else {
  12135. this._renderOther(data[0]);
  12136. }
  12137. this.fire('render');
  12138. }));
  12139. }));
  12140. },
  12141. /**
  12142. * Given this name, figure out the proper (English) pronoun for it.
  12143. */
  12144. _renderPronoun: function(userInfo) {
  12145. var
  12146. word = '',
  12147. objective = this._objective;
  12148. if (this._subjectId) {
  12149. objective = true;
  12150. if (this._subjectId === this._uid) {
  12151. this._reflexive = true;
  12152. }
  12153. }
  12154. if (this._uid == FB.Connect.get_loggedInUser() &&
  12155. this._getBoolAttribute('use-you', true)) {
  12156. if (this._possessive) {
  12157. if (this._reflexive) {
  12158. word = 'your own';
  12159. } else {
  12160. word = 'your';
  12161. }
  12162. } else {
  12163. if (this._reflexive) {
  12164. word = 'yourself';
  12165. } else {
  12166. word = 'you';
  12167. }
  12168. }
  12169. }
  12170. else {
  12171. switch (userInfo.sex) {
  12172. case 'male':
  12173. if (this._possessive) {
  12174. word = this._reflexive ? 'his own' : 'his';
  12175. } else {
  12176. if (this._reflexive) {
  12177. word = 'himself';
  12178. } else if (objective) {
  12179. word = 'him';
  12180. } else {
  12181. word = 'he';
  12182. }
  12183. }
  12184. break;
  12185. case 'female':
  12186. if (this._possessive) {
  12187. word = this._reflexive ? 'her own' : 'her';
  12188. } else {
  12189. if (this._reflexive) {
  12190. word = 'herself';
  12191. } else if (objective) {
  12192. word = 'her';
  12193. } else {
  12194. word = 'she';
  12195. }
  12196. }
  12197. break;
  12198. default:
  12199. if (this._getBoolAttribute('use-they', true)) {
  12200. if (this._possessive) {
  12201. if (this._reflexive) {
  12202. word = 'their own';
  12203. } else {
  12204. word = 'their';
  12205. }
  12206. } else {
  12207. if (this._reflexive) {
  12208. word = 'themselves';
  12209. } else if (objective) {
  12210. word = 'them';
  12211. } else {
  12212. word = 'they';
  12213. }
  12214. }
  12215. }
  12216. else {
  12217. if (this._possessive) {
  12218. if (this._reflexive) {
  12219. word = 'his/her own';
  12220. } else {
  12221. word = 'his/her';
  12222. }
  12223. } else {
  12224. if (this._reflexive) {
  12225. word = 'himself/herself';
  12226. } else if (objective) {
  12227. word = 'him/her';
  12228. } else {
  12229. word = 'he/she';
  12230. }
  12231. }
  12232. }
  12233. break;
  12234. }
  12235. }
  12236. if (this._getBoolAttribute('capitalize', false)) {
  12237. word = FB.Helper.upperCaseFirstChar(word);
  12238. }
  12239. this.dom.innerHTML = word;
  12240. },
  12241. /**
  12242. * Handle rendering of the element, using the
  12243. * metadata that came with it.
  12244. */
  12245. _renderOther: function(userInfo) {
  12246. var
  12247. name = '',
  12248. html = '';
  12249. if (this._uid == FB.Helper.getLoggedInUser() &&
  12250. this._getBoolAttribute('use-you', true)) {
  12251. if (this._reflexive) {
  12252. if (this._possessive) {
  12253. name = 'your own';
  12254. } else {
  12255. name = 'yourself';
  12256. }
  12257. } else {
  12258. // The possessive works really nicely this way!
  12259. if (this._possessive) {
  12260. name = 'your';
  12261. } else {
  12262. name = 'you';
  12263. }
  12264. }
  12265. }
  12266. else if (userInfo) {
  12267. // FQLCantSee structures will show as null.
  12268. if (null === userInfo.first_name) {
  12269. userInfo.first_name = '';
  12270. }
  12271. if (null === userInfo.last_name) {
  12272. userInfo.last_name = '';
  12273. }
  12274. // Structures that don't exist will return undefined
  12275. // (ie. this could happen for an app)
  12276. // In that case we just ignore firstnameonly and
  12277. // lastnameonly
  12278. if (this._firstnameonly && userInfo.first_name !== undefined) {
  12279. name = FB.String.escapeHTML(userInfo.first_name);
  12280. } else if (this._lastnameonly && userInfo.last_name !== undefined) {
  12281. name = FB.String.escapeHTML(userInfo.last_name);
  12282. }
  12283. if (!name) {
  12284. name = FB.String.escapeHTML(userInfo.name);
  12285. }
  12286. if (name !== '' && this._possessive) {
  12287. name += '\'s';
  12288. }
  12289. }
  12290. if (!name) {
  12291. name = FB.String.escapeHTML(
  12292. this.getAttribute('if-cant-see', 'Facebook User'));
  12293. }
  12294. if (name) {
  12295. if (this._getBoolAttribute('capitalize', false)) {
  12296. name = FB.Helper.upperCaseFirstChar(name);
  12297. }
  12298. if (userInfo && this._linked) {
  12299. html = FB.Helper.getProfileLink(userInfo, name,
  12300. this.getAttribute('href', null));
  12301. } else {
  12302. html = name;
  12303. }
  12304. }
  12305. this.dom.innerHTML = html;
  12306. }
  12307. });
  12308. /**
  12309. * Copyright Facebook Inc.
  12310. *
  12311. * Licensed under the Apache License, Version 2.0 (the "License");
  12312. * you may not use this file except in compliance with the License.
  12313. * You may obtain a copy of the License at
  12314. *
  12315. * http://www.apache.org/licenses/LICENSE-2.0
  12316. *
  12317. * Unless required by applicable law or agreed to in writing, software
  12318. * distributed under the License is distributed on an "AS IS" BASIS,
  12319. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12320. * See the License for the specific language governing permissions and
  12321. * limitations under the License.
  12322. *
  12323. * @provides fb.xfbml.profilepic
  12324. * @layer xfbml
  12325. * @requires fb.type
  12326. * fb.data
  12327. * fb.dom
  12328. * fb.helper
  12329. * fb.string
  12330. * fb.xfbml
  12331. * fb.xfbml.element
  12332. */
  12333. /**
  12334. * @class FB.XFBML.ProfilePic
  12335. * @extends FB.XFBML.Element
  12336. * @private
  12337. */
  12338. FB.subclass('XFBML.ProfilePic', 'XFBML.Element', null, {
  12339. /**
  12340. * Processes this tag.
  12341. */
  12342. process: function() {
  12343. var
  12344. size = this.getAttribute('size', 'thumb'),
  12345. picFieldName = FB.XFBML.ProfilePic._sizeToPicFieldMap[size],
  12346. width = this._getPxAttribute('width'),
  12347. height = this._getPxAttribute('height'),
  12348. style = this.dom.style,
  12349. uid = this.getAttribute('uid');
  12350. // Check if we need to add facebook logo image
  12351. if (this._getBoolAttribute('facebook-logo')) {
  12352. picFieldName += '_with_logo';
  12353. }
  12354. if (width) {
  12355. width = width + 'px';
  12356. style.width = width;
  12357. }
  12358. if (height) {
  12359. height = height + 'px';
  12360. style.height = height;
  12361. }
  12362. var renderFn = this.bind(function(result) {
  12363. var
  12364. userInfo = result ? result[0] : null,
  12365. imgSrc = userInfo ? userInfo[picFieldName] : null;
  12366. if (!imgSrc) {
  12367. // Create default
  12368. imgSrc = FB.getDomain('cdn') +
  12369. FB.XFBML.ProfilePic._defPicMap[picFieldName];
  12370. }
  12371. // Copy width, height style, and class name of fb:profile-pic down to the
  12372. // image element we create
  12373. var
  12374. styleValue = (
  12375. (width ? 'width:' + width + ';' : '') +
  12376. (height ? 'height:' + width + ';' : '')
  12377. ),
  12378. html = FB.String.format(
  12379. '<img src="{0}" alt="{1}" title="{1}" style="{2}" class="{3}" />',
  12380. imgSrc,
  12381. userInfo ? FB.String.escapeHTML(userInfo.name) : '',
  12382. styleValue,
  12383. this.dom.className
  12384. );
  12385. if (this._getBoolAttribute('linked', true)) {
  12386. html = FB.Helper.getProfileLink(
  12387. userInfo,
  12388. html,
  12389. this.getAttribute('href', null)
  12390. );
  12391. }
  12392. this.dom.innerHTML = html;
  12393. FB.Dom.addCss(this.dom, 'fb_profile_pic_rendered');
  12394. this.fire('render');
  12395. });
  12396. // Wait for status to be known
  12397. FB.Event.monitor('auth.statusChange', this.bind(function() {
  12398. //Is Element still in DOM tree
  12399. if (!this.isValid()) {
  12400. this.fire('render');
  12401. return true; // Stop processing
  12402. }
  12403. if (this.getAttribute('uid', null) == 'loggedinuser') {
  12404. uid = FB.Helper.getLoggedInUser();
  12405. }
  12406. // Is status known?
  12407. if (FB._userStatus && uid) {
  12408. // Get data
  12409. // Use profile if uid is a user, but a page
  12410. FB.Data._selectByIndex(
  12411. ['name', picFieldName],
  12412. FB.Helper.isUser(uid) ? 'user' : 'profile',
  12413. FB.Helper.isUser(uid) ? 'uid' : 'id',
  12414. uid
  12415. ).wait(renderFn);
  12416. } else {
  12417. // Render default
  12418. renderFn();
  12419. }
  12420. }));
  12421. }
  12422. });
  12423. FB.provide('XFBML.ProfilePic', {
  12424. /**
  12425. * Maps field type to placeholder/silhouette image.
  12426. *
  12427. * This dynamic data is replaced with rsrc.php backed URLs by Haste.
  12428. */
  12429. _defPicMap: {
  12430. pic : 'pics/s_silhouette.jpg',
  12431. pic_big : 'pics/d_silhouette.gif',
  12432. pic_big_with_logo : 'pics/d_silhouette_logo.gif',
  12433. pic_small : 'pics/t_silhouette.jpg',
  12434. pic_small_with_logo : 'pics/t_silhouette_logo.gif',
  12435. pic_square : 'pics/q_silhouette.gif',
  12436. pic_square_with_logo : 'pics/q_silhouette_logo.gif',
  12437. pic_with_logo : 'pics/s_silhouette_logo.gif'
  12438. },
  12439. /**
  12440. * Maps user specified attribute for size to a field type.
  12441. */
  12442. _sizeToPicFieldMap: {
  12443. n : 'pic_big',
  12444. normal : 'pic_big',
  12445. q : 'pic_square',
  12446. s : 'pic',
  12447. small : 'pic',
  12448. square : 'pic_square',
  12449. t : 'pic_small',
  12450. thumb : 'pic_small'
  12451. }
  12452. });
  12453. /**
  12454. * Copyright Facebook Inc.
  12455. *
  12456. * Licensed under the Apache License, Version 2.0 (the "License");
  12457. * you may not use this file except in compliance with the License.
  12458. * You may obtain a copy of the License at
  12459. *
  12460. * http://www.apache.org/licenses/LICENSE-2.0
  12461. *
  12462. * Unless required by applicable law or agreed to in writing, software
  12463. * distributed under the License is distributed on an "AS IS" BASIS,
  12464. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12465. * See the License for the specific language governing permissions and
  12466. * limitations under the License.
  12467. *
  12468. * @provides fb.xfbml.question
  12469. * @layer xfbml
  12470. * @requires fb.type
  12471. * fb.xfbml.iframewidget
  12472. */
  12473. /**
  12474. * Implementation for the fb:question tag.
  12475. *
  12476. * @class FB.XFBML.Question
  12477. * @extends FB.XFBML.IframeWidget
  12478. * @private
  12479. */
  12480. FB.subclass('XFBML.Question', 'XFBML.IframeWidget', null, {
  12481. _visibleAfter: 'load',
  12482. setupAndValidate: function() {
  12483. this._attr = {
  12484. channel : this.getChannelUrl(),
  12485. api_key : FB._apiKey,
  12486. permalink : this.getAttribute('permalink'),
  12487. id : this.getAttribute('id'),
  12488. width : this._getPxAttribute('width', 400),
  12489. height : 0,
  12490. questiontext : this.getAttribute('questiontext'),
  12491. options : this.getAttribute('options')
  12492. };
  12493. this.subscribe('xd.firstVote', FB.bind(this._onInitialVote, this));
  12494. this.subscribe('xd.vote', FB.bind(this._onChangedVote, this));
  12495. return true;
  12496. },
  12497. getSize: function() {
  12498. return { width: this._attr.width, height: this._attr.height };
  12499. },
  12500. getUrlBits: function() {
  12501. return { name: 'question', params: this._attr };
  12502. },
  12503. /**
  12504. * Invoked when the user initially votes on a question via the Question
  12505. * Plugin.
  12506. */
  12507. _onInitialVote: function(message) {
  12508. FB.Event.fire('question.firstVote', this._attr.permalink, message.vote);
  12509. },
  12510. /**
  12511. * Invoked when the user changes their vote on a question via the
  12512. * Question Plugin.
  12513. */
  12514. _onChangedVote: function(message) {
  12515. FB.Event.fire('question.vote', this._attr.permalink, message.vote);
  12516. }
  12517. });
  12518. /**
  12519. * Copyright Facebook Inc.
  12520. *
  12521. * Licensed under the Apache License, Version 2.0 (the "License");
  12522. * you may not use this file except in compliance with the License.
  12523. * You may obtain a copy of the License at
  12524. *
  12525. * http://www.apache.org/licenses/LICENSE-2.0
  12526. *
  12527. * Unless required by applicable law or agreed to in writing, software
  12528. * distributed under the License is distributed on an "AS IS" BASIS,
  12529. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12530. * See the License for the specific language governing permissions and
  12531. * limitations under the License.
  12532. *
  12533. * @provides fb.xfbml.recommendations
  12534. * @layer xfbml
  12535. * @requires fb.type fb.xfbml.iframewidget
  12536. */
  12537. /**
  12538. * Implementation for fb:recommendations tag.
  12539. *
  12540. * @class FB.XFBML.Recommendations
  12541. * @extends FB.XFBML.IframeWidget
  12542. * @private
  12543. */
  12544. FB.subclass('XFBML.Recommendations', 'XFBML.IframeWidget', null, {
  12545. _visibleAfter: 'load',
  12546. /**
  12547. * Refresh the iframe on auth.statusChange events.
  12548. */
  12549. _refreshOnAuthChange: true,
  12550. /**
  12551. * Do initial attribute processing.
  12552. */
  12553. setupAndValidate: function() {
  12554. this._attr = {
  12555. border_color : this.getAttribute('border-color'),
  12556. colorscheme : this.getAttribute('color-scheme'),
  12557. filter : this.getAttribute('filter'),
  12558. font : this.getAttribute('font'),
  12559. action : this.getAttribute('action'),
  12560. linktarget : this.getAttribute('linktarget', '_blank'),
  12561. max_age : this.getAttribute('max_age'),
  12562. header : this._getBoolAttribute('header'),
  12563. height : this._getPxAttribute('height', 300),
  12564. site : this.getAttribute('site', location.hostname),
  12565. width : this._getPxAttribute('width', 300)
  12566. };
  12567. return true;
  12568. },
  12569. /**
  12570. * Get the initial size.
  12571. *
  12572. * @return {Object} the size
  12573. */
  12574. getSize: function() {
  12575. return { width: this._attr.width, height: this._attr.height };
  12576. },
  12577. /**
  12578. * Get the URL bits for the iframe.
  12579. *
  12580. * @return {Object} the iframe URL bits
  12581. */
  12582. getUrlBits: function() {
  12583. return { name: 'recommendations', params: this._attr };
  12584. }
  12585. });
  12586. /**
  12587. * @provides fb.xfbml.recommendationsbar
  12588. * @requires fb.anim
  12589. * fb.arbiter
  12590. * fb.type
  12591. * fb.xfbml.iframewidget
  12592. * @css fb.css.plugin.recommendationsbar
  12593. * @layer xfbml
  12594. */
  12595. /**
  12596. * Copyright Facebook Inc.
  12597. *
  12598. * Licensed under the Apache License, Version 2.0 (the "License");
  12599. * you may not use this file except in compliance with the License.
  12600. * You may obtain a copy of the License at
  12601. *
  12602. * http://www.apache.org/licenses/LICENSE-2.0
  12603. *
  12604. * Unless required by applicable law or agreed to in writing, software
  12605. * distributed under the License is distributed on an "AS IS" BASIS,
  12606. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12607. * See the License for the specific language governing permissions and
  12608. * limitations under the License.
  12609. */
  12610. /**
  12611. * Implementation for <fb:recommendations-bar /> social plugin tag.
  12612. *
  12613. * @class FB.XFBML.RecommendationsBar
  12614. * @extends FB.XFBML.IframeWidget
  12615. * @private
  12616. */
  12617. FB.subclass(
  12618. 'XFBML.RecommendationsBar', 'XFBML.IframeWidget', null, {
  12619. getUrlBits: function() {
  12620. return { name: 'recommendations_bar', params: this._attr };
  12621. },
  12622. setupAndValidate: function() {
  12623. function interval_queue(interval, func) {
  12624. var last_run = 0;
  12625. var queued = null;
  12626. function run() {
  12627. func();
  12628. queued = null;
  12629. last_run = (new Date()).getTime();
  12630. }
  12631. return function() { // First Class Functions <3
  12632. if (!queued) {
  12633. var now = (new Date()).getTime();
  12634. if (now - last_run < interval) {
  12635. queued = window.setTimeout(run, interval - (now - last_run));
  12636. } else {
  12637. run();
  12638. }
  12639. }
  12640. return true;
  12641. };
  12642. }
  12643. function validate_trigger(trigger) {
  12644. if (trigger.match(/^\d+(?:\.\d+)?%$/)) {
  12645. // clip to [0, 100]
  12646. var percent = Math.min(Math.max(parseInt(trigger, 10), 0), 100);
  12647. trigger = percent / 100;
  12648. } else if (trigger != 'manual' && trigger != 'onvisible') {
  12649. trigger = 'onvisible';
  12650. }
  12651. return trigger;
  12652. }
  12653. function validate_read_time(read_time) {
  12654. return Math.max(parseInt(read_time, 10) || 30, 10);
  12655. }
  12656. function validate_side(side) {
  12657. if (side == 'left' || side == 'right') { // Explicitly Provided
  12658. return side;
  12659. }
  12660. return FB._localeIsRtl ? 'left' : 'right'; // Default Guess
  12661. }
  12662. this._attr = {
  12663. channel : this.getChannelUrl(),
  12664. api_key : FB._apiKey,
  12665. font : this.getAttribute('font'),
  12666. colorscheme : this.getAttribute('colorscheme'),
  12667. href : FB.URI.resolve(this.getAttribute('href')),
  12668. side : validate_side(this.getAttribute('side')),
  12669. site : this.getAttribute('site'),
  12670. action : this.getAttribute('action'),
  12671. ref : this.getAttribute('ref'),
  12672. max_age : this.getAttribute('max_age'),
  12673. trigger : validate_trigger(this.getAttribute('trigger', '')),
  12674. read_time : validate_read_time(this.getAttribute('read_time')),
  12675. num_recommendations :
  12676. parseInt(this.getAttribute('num_recommendations'), 10) || 2
  12677. };
  12678. // Right now this is used only to control the use of postMessage in
  12679. // FB.Arbiter. This is pretty hacky and should be done properly asap.
  12680. FB._inPlugin = true;
  12681. this._showLoader = false;
  12682. this.subscribe(
  12683. 'iframe.onload',
  12684. FB.bind(
  12685. function() {
  12686. var span = this.dom.children[0];
  12687. span.className = 'fbpluginrecommendationsbar' + this._attr.side;
  12688. },
  12689. this));
  12690. var action = FB.bind(
  12691. function() {
  12692. FB.Event.unlisten(window, 'scroll', action);
  12693. FB.Event.unlisten(document.documentElement, 'click', action);
  12694. FB.Event.unlisten(document.documentElement, 'mousemove', action);
  12695. window.setTimeout(
  12696. FB.bind(
  12697. this.arbiterInform,
  12698. this,
  12699. 'platform/plugins/recommendations_bar/action',
  12700. null,
  12701. FB.Arbiter.BEHAVIOR_STATE),
  12702. this._attr.read_time * 1000); // convert s to ms
  12703. return true;
  12704. }, this);
  12705. FB.Event.listen(window, 'scroll', action);
  12706. FB.Event.listen(document.documentElement, 'click', action);
  12707. FB.Event.listen(document.documentElement, 'mousemove', action);
  12708. if (this._attr.trigger == "manual") {
  12709. var manual = FB.bind(
  12710. function(href) {
  12711. if (href == this._attr.href) {
  12712. FB.Event.unsubscribe('xfbml.recommendationsbar.read', manual);
  12713. this.arbiterInform(
  12714. 'platform/plugins/recommendations_bar/trigger', null,
  12715. FB.Arbiter.BEHAVIOR_STATE);
  12716. }
  12717. return true;
  12718. }, this);
  12719. FB.Event.subscribe('xfbml.recommendationsbar.read', manual);
  12720. } else {
  12721. var trigger = interval_queue(
  12722. 500,
  12723. FB.bind(
  12724. function() {
  12725. if (this.calculateVisibility()) {
  12726. FB.Event.unlisten(window, 'scroll', trigger);
  12727. FB.Event.unlisten(window, 'resize', trigger);
  12728. this.arbiterInform(
  12729. 'platform/plugins/recommendations_bar/trigger', null,
  12730. FB.Arbiter.BEHAVIOR_STATE);
  12731. }
  12732. return true;
  12733. }, this));
  12734. FB.Event.listen(window, 'scroll', trigger);
  12735. FB.Event.listen(window, 'resize', trigger);
  12736. trigger(); // in case <fb:recommendations-bar /> is already visible
  12737. }
  12738. this.visible = false;
  12739. var visible = interval_queue(
  12740. 500,
  12741. FB.bind(
  12742. function() {
  12743. if (!this.visible && this.calculateVisibility()) {
  12744. this.visible = true;
  12745. this.arbiterInform(
  12746. 'platform/plugins/recommendations_bar/visible');
  12747. } else if (this.visible && !this.calculateVisibility()) {
  12748. this.visible = false;
  12749. this.arbiterInform(
  12750. 'platform/plugins/recommendations_bar/invisible');
  12751. }
  12752. return true;
  12753. }, this));
  12754. FB.Event.listen(window, 'scroll', visible);
  12755. FB.Event.listen(window, 'resize', visible);
  12756. visible(); // in case <fb:recommendations-bar /> is already visible
  12757. this.focused = true;
  12758. var toggleFocused = FB.bind(
  12759. function() {
  12760. this.focused = !this.focused;
  12761. return true;
  12762. }, this);
  12763. FB.Event.listen(window, 'blur', toggleFocused);
  12764. FB.Event.listen(window, 'focus', toggleFocused);
  12765. this.resize_running = false;
  12766. this.animate = false;
  12767. this.subscribe(
  12768. 'xd.signal_animation',
  12769. FB.bind(function() { this.animate = true; }, this));
  12770. return true;
  12771. },
  12772. getSize: function() {
  12773. // The width here is in Chrome; Firefox is 2px smaller in both cases.
  12774. return {
  12775. height: 25, width: (this._attr.action == 'recommend' ? 140 : 96) };
  12776. },
  12777. calculateVisibility: function() {
  12778. var fold = document.documentElement.clientHeight; // viewport height
  12779. // In Firefox, switching tabs causes viewport scrolling and size changes
  12780. // if Firebug is activated, so we avoid changing the visibility state when
  12781. // the document is not in focus. This has the unfortunate side effect of
  12782. // disabling the read action if the user clicks in the plugin (e.g. to
  12783. // expand the plugin or like the page) because these actions will transfer
  12784. // focus from this document to the plugn. So, we only perform this check
  12785. // if firebug is running.
  12786. if (!this.focused && window.console && window.console.firebug) {
  12787. return this.visible;
  12788. }
  12789. switch (this._attr.trigger) {
  12790. case "manual":
  12791. return false;
  12792. case "onvisible":
  12793. // <fb:recommendations-bar /> position, relative to the viewport
  12794. var elem = this.dom.getBoundingClientRect().top;
  12795. return elem <= fold;
  12796. default: // "80%", etc.
  12797. var scroll = window.pageYOffset || document.body.scrollTop;
  12798. var height = document.documentElement.scrollHeight; // doc height
  12799. return (scroll + fold) / height >= this._attr.trigger;
  12800. }
  12801. },
  12802. _handleResizeMsg: function(message) {
  12803. if (!this.isValid()) {
  12804. return;
  12805. }
  12806. if (message.width) {
  12807. this.getIframeNode().style.width = message.width + 'px';
  12808. }
  12809. if (message.height) {
  12810. this._setNextResize(message.height);
  12811. this._checkNextResize();
  12812. }
  12813. this._makeVisible();
  12814. },
  12815. _setNextResize: function(height) {
  12816. this.next_resize = height;
  12817. },
  12818. _checkNextResize: function() {
  12819. if (!this.next_resize || this.resize_running) {
  12820. return;
  12821. }
  12822. var iframe = this.getIframeNode();
  12823. var height = this.next_resize;
  12824. this.next_resize = null;
  12825. if (this.animate) {
  12826. this.animate = false; // the signal causes one resize to animate
  12827. this.resize_running = true;
  12828. FB.Anim.ate(
  12829. iframe, { height: height + 'px' }, 330, FB.bind(
  12830. function() {
  12831. this.resize_running = false;
  12832. this._checkNextResize();
  12833. }, this));
  12834. } else {
  12835. iframe.style.height = height + 'px';
  12836. }
  12837. }
  12838. });
  12839. /**
  12840. * This is the functions that the 3rd party host site can call to manually
  12841. * trigger the news.read action. This will work only if the `trigger="manual"`
  12842. * attribute is specified on the <fb:recommendations-bar /> tag.
  12843. */
  12844. FB.XFBML.RecommendationsBar.markRead = function(href) {
  12845. FB.Event.fire('xfbml.recommendationsbar.read', href || window.location.href);
  12846. };
  12847. /**
  12848. * Copyright Facebook Inc.
  12849. *
  12850. * Licensed under the Apache License, Version 2.0 (the "License");
  12851. * you may not use this file except in compliance with the License.
  12852. * You may obtain a copy of the License at
  12853. *
  12854. * http://www.apache.org/licenses/LICENSE-2.0
  12855. *
  12856. * Unless required by applicable law or agreed to in writing, software
  12857. * distributed under the License is distributed on an "AS IS" BASIS,
  12858. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12859. * See the License for the specific language governing permissions and
  12860. * limitations under the License.
  12861. *
  12862. * @provides fb.xfbml.registration
  12863. * @layer xfbml
  12864. * @requires fb.arbiter
  12865. * fb.helper
  12866. * fb.json
  12867. * fb.type
  12868. * fb.xfbml.iframewidget
  12869. * fb.prelude
  12870. * fb.event
  12871. */
  12872. /**
  12873. * Implementation for fb:registration tag.
  12874. *
  12875. * @class FB.XFBML.Registration
  12876. * @extends FB.XFBML.IframeWidget
  12877. * @private
  12878. */
  12879. FB.subclass('XFBML.Registration', 'XFBML.IframeWidget', null, {
  12880. _visibleAfter: 'immediate',
  12881. /** Constants for guessing the height **/
  12882. // Logged out is 139 and logged in is 167, so lets make it nice
  12883. // for logged in people
  12884. _baseHeight: 167,
  12885. // How much taller does adding 1 field make the plugin
  12886. _fieldHeight: 28,
  12887. // Widths below this make the widget render with labels above the fields
  12888. _skinnyWidth: 520,
  12889. // This one is tough since the text wraps so much, but this is assuming
  12890. // 2 lines of text on the top, and 3 lines on the bottom
  12891. _skinnyBaseHeight: 173,
  12892. // Assuming the labels are above, adding a field height
  12893. _skinnyFieldHeight: 52,
  12894. /**
  12895. * Do initial attribute processing.
  12896. */
  12897. setupAndValidate: function() {
  12898. this._attr = {
  12899. action : this.getAttribute('action'),
  12900. border_color : this.getAttribute('border-color'),
  12901. channel_url : this.getChannelUrl(),
  12902. client_id : FB._apiKey,
  12903. fb_only : this._getBoolAttribute('fb-only', false),
  12904. fb_register : this._getBoolAttribute('fb-register', false),
  12905. fields : this.getAttribute('fields'),
  12906. height : this._getPxAttribute('height'),
  12907. redirect_uri : this.getAttribute('redirect-uri', window.location.href),
  12908. no_footer : this._getBoolAttribute('no-footer'),
  12909. no_header : this._getBoolAttribute('no-header'),
  12910. onvalidate : this.getAttribute('onvalidate'),
  12911. width : this._getPxAttribute('width', 600),
  12912. target : this.getAttribute('target')
  12913. };
  12914. // All errors will be handled in the iframe, and they show more obvious
  12915. // messages than FB.log
  12916. if (this._attr.onvalidate) {
  12917. this.subscribe('xd.validate', this.bind(function(message) {
  12918. var value = FB.JSON.parse(message.value);
  12919. var callback = this.bind(function(errors) {
  12920. // Send the message back to facebook
  12921. FB.Arbiter.inform('Registration.Validation',
  12922. {errors: errors, id: message.id},
  12923. 'parent.frames["' +
  12924. this.getIframeNode().name +
  12925. '"]',
  12926. this._attr.channel_url.substring(0, 5) == "https");
  12927. });
  12928. // Call their function
  12929. var response = FB.Helper.executeFunctionByName(this._attr.onvalidate,
  12930. value, callback);
  12931. // If they returned anything, call the callback
  12932. if (response) {
  12933. callback(response);
  12934. }
  12935. }));
  12936. }
  12937. this.subscribe('xd.authLogin', FB.bind(this._onAuthLogin, this));
  12938. this.subscribe('xd.authLogout', FB.bind(this._onAuthLogout, this));
  12939. return true;
  12940. },
  12941. /**
  12942. * Get the initial size.
  12943. *
  12944. * @return {Object} the size
  12945. */
  12946. getSize: function() {
  12947. return { width: this._attr.width, height: this._getHeight() };
  12948. },
  12949. _getHeight: function() {
  12950. if (this._attr.height) {
  12951. return this._attr.height;
  12952. }
  12953. var fields;
  12954. if (!this._attr.fields) {
  12955. // by default, only the name field
  12956. fields = ['name'];
  12957. } else {
  12958. try {
  12959. // JSON
  12960. fields = FB.JSON.parse(this._attr.fields);
  12961. } catch (e) {
  12962. // CSV
  12963. fields = this._attr.fields.split(/,/);
  12964. }
  12965. }
  12966. if (this._attr.width < this._skinnyWidth) {
  12967. return this._skinnyBaseHeight + fields.length * this._skinnyFieldHeight;
  12968. } else {
  12969. return this._baseHeight + fields.length * this._fieldHeight;
  12970. }
  12971. },
  12972. /**
  12973. * Get the URL bits for the iframe.
  12974. *
  12975. * @return {Object} the iframe URL bits
  12976. */
  12977. getUrlBits: function() {
  12978. return { name: 'registration', params: this._attr };
  12979. },
  12980. /**
  12981. * Returns the default domain that should be used for the
  12982. * registration plugin being served from the web tier. Because
  12983. * of the complexities involved in serving up the registration
  12984. * plugin for logged out and logged in HTTPS users, and because
  12985. * a near-majority of registration plugins are rendered for logged out
  12986. * users, we just always load the registration plugin over https.
  12987. * Of particular note, redirects from HTTP to HTTPS for logged-in
  12988. * users with account security on cause configuration data to be lost
  12989. * because the large amount of configuration data must be sent via POST
  12990. * instead of GET because of GET limits.
  12991. */
  12992. getDefaultWebDomain: function() {
  12993. return 'https_www';
  12994. },
  12995. /**
  12996. * Invoked when the user logs in
  12997. */
  12998. _onAuthLogin: function() {
  12999. if (!FB.getAuthResponse()) {
  13000. FB.getLoginStatus();
  13001. }
  13002. FB.Helper.fireEvent('auth.login', this);
  13003. },
  13004. /**
  13005. * Invoked when the user logs out
  13006. */
  13007. _onAuthLogout: function() {
  13008. if (!FB.getAuthResponse()) {
  13009. FB.getLoginStatus();
  13010. }
  13011. FB.Helper.fireEvent('auth.logout', this);
  13012. }
  13013. });
  13014. /**
  13015. * Copyright Facebook Inc.
  13016. *
  13017. * Licensed under the Apache License, Version 2.0 (the "License");
  13018. * you may not use this file except in compliance with the License.
  13019. * You may obtain a copy of the License at
  13020. *
  13021. * http://www.apache.org/licenses/LICENSE-2.0
  13022. *
  13023. * Unless required by applicable law or agreed to in writing, software
  13024. * distributed under the License is distributed on an "AS IS" BASIS,
  13025. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13026. * See the License for the specific language governing permissions and
  13027. * limitations under the License.
  13028. *
  13029. * @provides fb.xfbml.serverfbml
  13030. * @layer xfbml
  13031. * @requires fb.type fb.content fb.xfbml.iframewidget fb.auth
  13032. */
  13033. /**
  13034. * Implementation for fb:serverfbml tag.
  13035. *
  13036. * @class FB.XFBML.ServerFbml
  13037. * @extends FB.XFBML.IframeWidget
  13038. * @private
  13039. */
  13040. FB.subclass('XFBML.ServerFbml', 'XFBML.IframeWidget', null, {
  13041. /**
  13042. * Make the iframe visible only when we get the initial resize message.
  13043. */
  13044. _visibleAfter: 'resize',
  13045. /**
  13046. * Do initial attribute processing.
  13047. */
  13048. setupAndValidate: function() {
  13049. // query parameters to the comments iframe
  13050. this._attr = {
  13051. channel_url : this.getChannelUrl(),
  13052. fbml : this.getAttribute('fbml'),
  13053. width : this._getPxAttribute('width')
  13054. };
  13055. // fbml may also be specified as a child script tag
  13056. if (!this._attr.fbml) {
  13057. var child = this.dom.getElementsByTagName('script')[0];
  13058. if (child && child.type === 'text/fbml') {
  13059. this._attr.fbml = child.innerHTML;
  13060. }
  13061. }
  13062. // if still no fbml, error
  13063. if (!this._attr.fbml) {
  13064. FB.log('<fb:serverfbml> requires the "fbml" attribute.');
  13065. return false;
  13066. }
  13067. return true;
  13068. },
  13069. /**
  13070. * Get the initial size.
  13071. *
  13072. * @return {Object} the size
  13073. */
  13074. getSize: function() {
  13075. return { width: this._attr.width, height: this._attr.height };
  13076. },
  13077. /**
  13078. * Get the URL bits for the iframe.
  13079. *
  13080. * @return {Object} the iframe URL bits
  13081. */
  13082. getUrlBits: function() {
  13083. return { name: 'serverfbml', params: this._attr };
  13084. }
  13085. });
  13086. /**
  13087. * @provides fb.xfbml.sharebutton
  13088. * @requires fb.data
  13089. * fb.dom
  13090. * fb.helper
  13091. * fb.intl
  13092. * fb.string
  13093. * fb.type
  13094. * fb.ui
  13095. * fb.xfbml
  13096. * fb.xfbml.element
  13097. * @css fb.css.sharebutton
  13098. * @layer xfbml
  13099. */
  13100. /**
  13101. * Copyright Facebook Inc.
  13102. *
  13103. * Licensed under the Apache License, Version 2.0 (the "License");
  13104. * you may not use this file except in compliance with the License.
  13105. * You may obtain a copy of the License at
  13106. *
  13107. * http://www.apache.org/licenses/LICENSE-2.0
  13108. *
  13109. * Unless required by applicable law or agreed to in writing, software
  13110. * distributed under the License is distributed on an "AS IS" BASIS,
  13111. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13112. * See the License for the specific language governing permissions and
  13113. * limitations under the License.
  13114. */
  13115. /**
  13116. * Implementation for fb:share-button tag.
  13117. * @class FB.XFBML.ShareButton
  13118. * @extends FB.XFBML.Element
  13119. * @private
  13120. */
  13121. FB.subclass('XFBML.ShareButton', 'XFBML.Element', null, {
  13122. /**
  13123. * Processes this tag.
  13124. */
  13125. process: function() {
  13126. this._href = this.getAttribute('href', window.location.href);
  13127. //TODO: When we turn sharepro on, replace icon_link with button_count
  13128. this._type = this.getAttribute('type', 'icon_link');
  13129. FB.Dom.addCss(this.dom, 'fb_share_count_hidden'); // start off assuming 0
  13130. this._renderButton(true);
  13131. },
  13132. /**
  13133. * Render's the button.
  13134. *
  13135. * @access private
  13136. * @param skipRenderEvent {Boolean} indicate if firing of the render event
  13137. * should be skipped. This is useful because the _renderButton() function may
  13138. * recursively call itself to do the final render, which is when we want to
  13139. * fire the render event.
  13140. */
  13141. _renderButton: function(skipRenderEvent) {
  13142. if (!this.isValid()) {
  13143. this.fire('render');
  13144. return;
  13145. }
  13146. var
  13147. contentStr = '',
  13148. post = '',
  13149. pre = '',
  13150. classStr = '',
  13151. share = FB.Intl.tx._("Share"),
  13152. wrapperClass = '';
  13153. switch (this._type) {
  13154. case 'icon':
  13155. case 'icon_link':
  13156. classStr = 'fb_button_simple';
  13157. contentStr = (
  13158. '<span class="fb_button_text">' +
  13159. (this._type == 'icon_link' ? share : '&nbsp;') +
  13160. '</span>'
  13161. );
  13162. skipRenderEvent = false;
  13163. break;
  13164. case 'link':
  13165. contentStr = FB.Intl.tx._("Share on Facebook");
  13166. skipRenderEvent = false;
  13167. break;
  13168. case 'button':
  13169. contentStr = '<span class="fb_button_text">' + share + '</span>';
  13170. classStr = 'fb_button fb_button_small';
  13171. skipRenderEvent = false;
  13172. break;
  13173. case 'button_count':
  13174. contentStr = '<span class="fb_button_text">' + share + '</span>';
  13175. post = (
  13176. '<span class="fb_share_count_nub_right">&nbsp;</span>' +
  13177. '<span class="fb_share_count fb_share_count_right">'+
  13178. this._getCounterMarkup() +
  13179. '</span>'
  13180. );
  13181. classStr = 'fb_button fb_button_small';
  13182. break;
  13183. default:
  13184. // box count
  13185. contentStr = '<span class="fb_button_text">' + share + '</span>';
  13186. pre = (
  13187. '<span class="fb_share_count_nub_top">&nbsp;</span>' +
  13188. '<span class="fb_share_count fb_share_count_top">' +
  13189. this._getCounterMarkup() +
  13190. '</span>'
  13191. );
  13192. classStr = 'fb_button fb_button_small';
  13193. wrapperClass = 'fb_share_count_wrapper';
  13194. }
  13195. var a_id = FB.guid();
  13196. this.dom.innerHTML = FB.String.format(
  13197. '<span class="{0}">{4}<a id="{1}" class="{2}" ' +
  13198. 'target="_blank">{3}</a>{5}</span>',
  13199. wrapperClass,
  13200. a_id,
  13201. classStr,
  13202. contentStr,
  13203. pre,
  13204. post
  13205. );
  13206. var a = document.getElementById(a_id);
  13207. a.href = this._href;
  13208. a.onclick = function() {
  13209. FB.ui({ method: 'stream.share', u: this.href});
  13210. return false;
  13211. };
  13212. if (!skipRenderEvent) {
  13213. this.fire('render');
  13214. }
  13215. },
  13216. _getCounterMarkup: function() {
  13217. if (!this._count) {
  13218. this._count = FB.Data._selectByIndex(
  13219. ['total_count'],
  13220. 'link_stat',
  13221. 'url',
  13222. this._href
  13223. );
  13224. }
  13225. var prettyCount = '0';
  13226. if (this._count.value !== undefined) {
  13227. if (this._count.value.length > 0) {
  13228. var c = this._count.value[0].total_count;
  13229. if (c > 3) {
  13230. // now we want it to be visible
  13231. FB.Dom.removeCss(this.dom, 'fb_share_count_hidden');
  13232. prettyCount = c >= 10000000 ? Math.round(c/1000000) + 'M' :
  13233. (c >= 10000 ? Math.round(c/1000) + 'K' : c);
  13234. }
  13235. }
  13236. } else {
  13237. this._count.wait(FB.bind(this._renderButton, this, false));
  13238. }
  13239. return '<span class="fb_share_count_inner">' + prettyCount + '</span>';
  13240. }
  13241. });
  13242. /**
  13243. * Copyright Facebook Inc.
  13244. *
  13245. * Licensed under the Apache License, Version 2.0 (the "License");
  13246. * you may not use this file except in compliance with the License.
  13247. * You may obtain a copy of the License at
  13248. *
  13249. * http://www.apache.org/licenses/LICENSE-2.0
  13250. *
  13251. * Unless required by applicable law or agreed to in writing, software
  13252. * distributed under the License is distributed on an "AS IS" BASIS,
  13253. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13254. * See the License for the specific language governing permissions and
  13255. * limitations under the License.
  13256. *
  13257. * @provides fb.xfbml.socialcontext
  13258. * @layer xfbml
  13259. * @requires fb.type fb.xfbml.iframewidget fb.xfbml.facepile fb.auth
  13260. */
  13261. /**
  13262. * Implementation for fb:social-context tag.
  13263. *
  13264. * @class FB.XFBML.SocialContext
  13265. * @extends FB.XFBML.IframeWidget
  13266. * @private
  13267. */
  13268. FB.subclass('XFBML.SocialContext', 'XFBML.IframeWidget', null, {
  13269. /**
  13270. * Do initial attribute processing.
  13271. */
  13272. setupAndValidate: function() {
  13273. var size = this.getAttribute('size', 'small');
  13274. this._attr = {
  13275. channel: this.getChannelUrl(),
  13276. width: this._getPxAttribute('width', 400),
  13277. height: this._getPxAttribute('height', 100),
  13278. ref: this.getAttribute('ref'),
  13279. size: this.getAttribute('size'),
  13280. keywords: this.getAttribute('keywords'),
  13281. urls: this.getAttribute('urls')
  13282. };
  13283. return true;
  13284. },
  13285. /**
  13286. * Get the initial size.
  13287. *
  13288. * @return {Object} the size
  13289. */
  13290. getSize: function() {
  13291. return { width: this._attr.width, height: this._attr.height };
  13292. },
  13293. /**
  13294. * Get the URL bits for the iframe.
  13295. *
  13296. * @return {Object} the iframe URL bits
  13297. */
  13298. getUrlBits: function() {
  13299. return { name: 'social_context', params: this._attr };
  13300. }
  13301. });
  13302. /**
  13303. * Copyright Facebook Inc.
  13304. *
  13305. * Licensed under the Apache License, Version 2.0 (the "License");
  13306. * you may not use this file except in compliance with the License.
  13307. * You may obtain a copy of the License at
  13308. *
  13309. * http://www.apache.org/licenses/LICENSE-2.0
  13310. *
  13311. * Unless required by applicable law or agreed to in writing, software
  13312. * distributed under the License is distributed on an "AS IS" BASIS,
  13313. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13314. * See the License for the specific language governing permissions and
  13315. * limitations under the License.
  13316. *
  13317. * @provides fb.xfbml.subscribe
  13318. * @layer xfbml
  13319. * @requires fb.type
  13320. * fb.xfbml.edgewidget
  13321. */
  13322. /**
  13323. * Implementation for the fb:subscribe tag.
  13324. *
  13325. * @class FB.XFBML.Subscribe
  13326. * @extends FB.XFBML.EdgeWidget
  13327. * @private
  13328. */
  13329. FB.subclass('XFBML.Subscribe', 'XFBML.EdgeWidget', null, {
  13330. /**
  13331. * Do initial attribute processing.
  13332. */
  13333. setupAndValidate: function() {
  13334. this._attr = {
  13335. channel : this.getChannelUrl(),
  13336. api_key : FB._apiKey,
  13337. font : this.getAttribute('font'),
  13338. colorscheme : this.getAttribute('colorscheme', 'light'),
  13339. href : this.getAttribute('href'),
  13340. ref : this.getAttribute('ref'),
  13341. layout : this._getLayout(),
  13342. show_faces : this._shouldShowFaces(),
  13343. width : this._getWidgetWidth()
  13344. };
  13345. return true;
  13346. },
  13347. /**
  13348. * Get the URL bits for the iframe.
  13349. *
  13350. * @return {Object} the iframe URL bits
  13351. */
  13352. getUrlBits: function() {
  13353. return { name: 'subscribe', params: this._attr };
  13354. },
  13355. /**
  13356. * Returns the width of the widget iframe, taking into
  13357. * account the chosen layout, the user supplied width, and
  13358. * the min and max values we'll allow. There is much more
  13359. * flexibility in how wide the widget is, so a user-supplied
  13360. * width just needs to fall within a certain range.
  13361. *
  13362. * @return {String} the CSS-legitimate width in pixels, as
  13363. * with '460px'.
  13364. */
  13365. _getWidgetWidth : function() {
  13366. var layout = this._getLayout();
  13367. var layoutToDefaultWidthMap = {
  13368. 'standard': 450,
  13369. 'box_count': 83,
  13370. 'button_count': 115
  13371. };
  13372. var defaultWidth = layoutToDefaultWidthMap[layout];
  13373. var width = this._getPxAttribute('width', defaultWidth);
  13374. var allowedWidths = {
  13375. 'standard': {'min': 225, 'max': 900},
  13376. 'box_count': {'min': 43, 'max': 900},
  13377. 'button_count': {'min': 63, 'max': 900}
  13378. };
  13379. if (width < allowedWidths[layout].min) {
  13380. width = allowedWidths[layout].min;
  13381. } else if (width > allowedWidths[layout].max) {
  13382. width = allowedWidths[layout].max;
  13383. }
  13384. return width;
  13385. }
  13386. });
  13387. /**
  13388. * A meta component which requires everything.
  13389. *
  13390. * @nolint
  13391. * @provides fb.all
  13392. * @requires
  13393. * fb.api
  13394. * fb.auth
  13395. * fb.canvas
  13396. * fb.canvas.insights
  13397. * fb.canvas.prefetcher
  13398. * fb.compat.ui
  13399. * fb.compat.xfbml
  13400. * fb.data
  13401. * fb.frictionless
  13402. * fb.init
  13403. * fb.init.helper
  13404. * fb.nativecalls
  13405. * fb.pay
  13406. * fb.template_data
  13407. * fb.template_ui
  13408. * fb.ui.methods
  13409. * fb.uri
  13410. * fb.xfbml.activity
  13411. * fb.xfbml.addprofiletab
  13412. * fb.xfbml.addtotimeline
  13413. * fb.xfbml.bookmark
  13414. * fb.xfbml.comments
  13415. * fb.xfbml.commentscount
  13416. * fb.xfbml.connectbar
  13417. * fb.xfbml.element
  13418. * fb.xfbml.facepile
  13419. * fb.xfbml.fan
  13420. * fb.xfbml.friendpile
  13421. * fb.xfbml.like
  13422. * fb.xfbml.likebox
  13423. * fb.xfbml.livestream
  13424. * fb.xfbml.login
  13425. * fb.xfbml.loginbutton
  13426. * fb.xfbml.name
  13427. * fb.xfbml.profilepic
  13428. * fb.xfbml.question
  13429. * fb.xfbml.recommendations
  13430. * fb.xfbml.recommendationsbar
  13431. * fb.xfbml.registration
  13432. * fb.xfbml.send
  13433. * fb.xfbml.serverfbml
  13434. * fb.xfbml.sharebutton
  13435. * fb.xfbml.socialcontext
  13436. * fb.xfbml.subscribe
  13437. */
  13438. void(0);
  13439. //FB.provide("", {"_domain":{"api":"https:\/\/api.facebook.com\/","api_read":"https:\/\/api-read.facebook.com\/","cdn":"https:\/\/s-static.facebook.com\/","cdn_foreign":"https:\/\/s-static.facebook.com\/","graph":"https:\/\/graph.facebook.com\/","https_cdn":"https:\/\/s-static.facebook.com\/","https_staticfb":"https:\/\/www.facebook.com\/","https_www":"https:\/\/www.facebook.com\/","staticfb":"http:\/\/www.facebook.com\/","www":"https:\/\/www.facebook.com\/","m":"https:\/\/m.facebook.com\/","https_m":"https:\/\/m.facebook.com\/"},"_locale":"en_US","_localeIsRtl":false}, true);
  13440. //FB.provide("Flash", {"_minVersions":[[10,3,181,34],[11,0,0]],"_swfPath":"rsrc.php\/v1\/yQ\/r\/f3KaqM7xIBg.swf"}, true);
  13441. //FB.provide("XD", {"_xdProxyUrl":"connect\/xd_proxy.php?version=3"}, true);
  13442. //FB.provide("Arbiter", {"_canvasProxyUrl":"connect\/canvas_proxy.php?version=3"}, true);
  13443. //FB.provide('Auth', {"_xdStorePath":"xd_localstorage\/v2"}, true);
  13444. //FB.provide("Canvas.Prefetcher", {"_appIdsBlacklist":[144959615576466],"_sampleRate":500}, true);
  13445. //FB.initSitevars = {"parseXFBMLBeforeDomReady":false,"computeContentSizeVersion":0,"enableMobile":1,"enableMobileComments":1,"forceSecureXdProxy":1,"iframePermissions":{"read_stream":false,"manage_mailbox":false,"manage_friendlists":false,"read_mailbox":false,"publish_checkins":true,"status_update":true,"photo_upload":true,"video_upload":true,"sms":false,"create_event":true,"rsvp_event":true,"offline_access":true,"email":true,"xmpp_login":false,"create_note":true,"share_item":true,"export_stream":false,"publish_stream":true,"publish_likes":true,"ads_management":false,"contact_email":true,"access_private_data":false,"read_insights":false,"read_requests":false,"read_friendlists":true,"manage_pages":false,"physical_login":false,"manage_groups":false,"read_deals":false}}; FB.forceOAuth = true; FB.widgetPipeEnabledApps = {"111476658864976":1,"cca6477272fc5cb805f85a84f20fca1d":1,"179150165472010":1}; FB.widgetPipeTagCountThreshold = 4;
  13446. //FB.provide("TemplateData", {"_enabled":true}, true);
  13447. //FB.provide("TemplateUI", {"_version":19}, true);
  13448. //FB.provide("XFBML.ConnectBar", {"imgs":{"buttonUrl":"rsrc.php\/v1\/yY\/r\/h_Y6u1wrZPW.png","missingProfileUrl":"rsrc.php\/v1\/yo\/r\/UlIqmHJn-SK.gif"}}, true);
  13449. //FB.provide("XFBML.ProfilePic", {"_defPicMap":{"pic":"rsrc.php\/v1\/yh\/r\/C5yt7Cqf3zU.jpg","pic_big":"rsrc.php\/v1\/yL\/r\/HsTZSDw4avx.gif","pic_big_with_logo":"rsrc.php\/v1\/y5\/r\/SRDCaeCL7hM.gif","pic_small":"rsrc.php\/v1\/yi\/r\/odA9sNLrE86.jpg","pic_small_with_logo":"rsrc.php\/v1\/yD\/r\/k1xiRXKnlGd.gif","pic_square":"rsrc.php\/v1\/yo\/r\/UlIqmHJn-SK.gif","pic_square_with_logo":"rsrc.php\/v1\/yX\/r\/9dYJBPDHXwZ.gif","pic_with_logo":"rsrc.php\/v1\/yu\/r\/fPPR9f2FJ3t.gif"}}, true);