1 /**
  2 * Copyright (c) 2014, salesforce.com, inc.
  3 * All rights reserved.
  4 *
  5 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
  6 * that the following conditions are met:
  7 *
  8 * Redistributions of source code must retain the above copyright notice, this list of conditions and the
  9 * following disclaimer.
 10 *
 11 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 12 * the following disclaimer in the documentation and/or other materials provided with the distribution.
 13 *
 14 * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
 15 * promote products derived from this software without specific prior written permission.
 16 *
 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 19 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 20 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 21 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 23 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 24 * POSSIBILITY OF SUCH DAMAGE.
 25 */
 26 
 27 /**
 28  *@namespace Sfdc.canvas.client
 29  *@name Sfdc.canvas.client
 30  */
 31 (function ($$) {
 32 
 33     "use strict";
 34 
 35     var pversion, cversion = "31.0";
 36 
 37     var module =   (function() /**@lends module */ {
 38 
 39         var purl;
 40 
 41         // returns the url of the Parent Window
 42         function getTargetOrigin(to) {
 43             var h;
 44             if (to === "*") {return to;}
 45             if (!$$.isNil(to)) {
 46                 h = $$.stripUrl(to);
 47                 purl = $$.startsWithHttp(h, purl);
 48                 if (purl) {return purl;}
 49             }
 50             // This relies on the parent passing it in. This may not be there as the client can do a redirect.
 51             h = $$.document().location.hash;
 52             if (h) {
 53                 h = decodeURIComponent(h.replace(/^#/, ''));
 54                 purl = $$.startsWithHttp(h, purl);
 55             }
 56             return purl;
 57         }
 58 
 59         // The main cross domain callback handler
 60         function xdCallback(data) {
 61             if (data) {
 62                 if (submodules[data.type]) {
 63                     submodules[data.type].callback(data);
 64                 }
 65                 // Just ignore...
 66             }
 67         }
 68 
 69         var submodules = (function () {
 70 
 71             var cbs = [], seq = 0, autog = true;
 72 
 73             // Functions common to submodules...
 74 
 75             function postit(clientscb, message) {
 76                 var wrapped, to, c;
 77 
 78                 // need to keep a mapping from request to callback, otherwise
 79                 // wrong callbacks get called. Unfortunately, this is the only
 80                 // way to handle this as postMessage acts more like topic/queue.
 81                 // limit the sequencers to 100 avoid out of memory errors
 82                 seq = (seq > 100) ? 0 : seq + 1;
 83                 cbs[seq] = clientscb;
 84                 wrapped = {seq : seq, src : "client", clientVersion : cversion, parentVersion: pversion, body : message};
 85 
 86                 c  = message && message.config && message.config.client;
 87                 to = getTargetOrigin($$.isNil(c) ? null : c.targetOrigin);
 88                 if ($$.isNil(to)) {
 89                     throw "ERROR: targetOrigin was not supplied and was not found on the hash tag, this can result from a redirect or link to another page.";
 90                 }
 91                 $$.console.log("posting message ", {message : wrapped, to : to});
 92                 $$.xd.post(wrapped, to, parent);
 93             }
 94 
 95             function validateClient(client, cb) {
 96                 var msg;
 97 
 98                 client = client || $$.oauth && $$.oauth.client();
 99 
100                 if ($$.isNil(client) || $$.isNil(client.oauthToken)) {
101                     msg = {status : 401, statusText : "Unauthorized" , parentVersion : pversion, payload : "client or client.oauthToken not supplied"};
102                 }
103                 if ($$.isNil(client.instanceId) || $$.isNil(client.targetOrigin)) {
104                     msg = {status : 400, statusText : "Bad Request" , parentVersion : pversion, payload : "client.instanceId or client.targetOrigin not supplied"};
105                 }
106                 if (!$$.isNil(msg)) {
107                     if ($$.isFunction(cb)) {
108                         cb(msg);
109                         return false;
110                     }
111                     else {
112                         throw msg;
113                     }
114                 }
115                 return true;
116             }
117 
118             // Submodules...
119 
120             var event = (function() {
121                 var subscriptions = {}, STR_EVT = "sfdc.streamingapi";
122 
123                 function validName(name, res) {
124                     var msg, r = $$.validEventName(name, res);
125                     if (r !== 0) {
126                         msg = {1 : "Event names can only contain one namespace",
127                                2 : "Namespace has already been reserved",
128                                3 : "Event name contains invalid characters"
129                         };
130                         throw msg[r];
131                     }
132                 }
133 
134                 function findSubscription(event) {
135                     var s, name = event.name;
136 
137                     if (name === STR_EVT) {
138                         if (!$$.isNil(subscriptions[name])) {
139                             s = subscriptions[name][event.params.topic];
140                         }
141                     } else {
142                         s = subscriptions[name];
143                     }
144 
145                     if (!$$.isNil(s) && ($$.isFunction(s.onData) || $$.isFunction(s.onComplete))) {
146                         return s;
147                     }
148 
149                     return null;
150                 }
151 
152                 return  {
153                     callback : function (data) {
154                         var event = data.payload,
155                             subscription = findSubscription(event),
156                             func;
157 
158                         if (!$$.isNil(subscription)) {
159                             if (event.method === "onData") {
160                                 func = subscription.onData;
161                             } else if (event.method === "onComplete") {
162                                 func = subscription.onComplete;
163                             }
164 
165                             if (!$$.isNil(func) && $$.isFunction(func)) {
166                                 func(event.payload);
167                             }
168                         }
169                     },
170 
171                     /**
172                      * @description Subscribes to parent or custom events. Events
173                      * with the namespaces 'canvas', 'sfdc', 'force', 'salesforce', and 'chatter' are reserved by Salesforce.
174                      * Developers can choose their own namespace and event names.
175                      * Event names must be in the form <code>namespace.eventname</code>.
176                      * @public
177                      * @name Sfdc.canvas.client#subscribe
178                      * @function
179                      * @param {client} client The object from the signed request
180                      * @param {Object} s The subscriber object or array of objects with name and callback functions
181                      * @example
182                      * // Subscribe to the parent window onscroll event.
183                      * Sfdc.canvas(function() {
184                      *     sr = JSON.parse('<%=signedRequestJson%>');
185                      *     // Capture the onScrolling event of the parent window.
186                      *     Sfdc.canvas.client.subscribe(sr.client,
187                      *          {name : 'canvas.scroll', onData : function (event) {
188                      *              console.log("Parent's contentHeight; " + event.heights.contentHeight);
189                      *              console.log("Parent's pageHeight; " + event.heights.pageHeight);
190                      *              console.log("Parent's scrollTop; " + event.heights.scrollTop);
191                      *              console.log("Parent's contentWidth; " + event.widths.contentWidth);
192                      *              console.log("Parent's pageWidth; " + event.widths.pageWidth);
193                      *              console.log("Parent's scrollLeft; " + event.widths.scrollLeft);
194                      *          }}
195                      *     );
196                      * });
197                      *
198                      * @example
199                      * // Subscribe to a custom event.
200                      * Sfdc.canvas(function() {
201                      *     sr = JSON.parse('<%=signedRequestJson%>');
202                      *     Sfdc.canvas.client.subscribe(sr.client,
203                      *         {name : 'mynamespace.someevent', onData : function (event) {
204                      *             console.log("Got custom event ",  event);
205                      *         }}
206                      *     );
207                      * });
208                      *
209                      * @example
210                      * // Subscribe to multiple events
211                      * Sfdc.canvas(function() {
212                      *     sr = JSON.parse('<%=signedRequestJson%>');
213                      *     Sfdc.canvas.client.subscribe(sr.client, [
214                      *         {name : 'mynamespace.someevent1', onData : handler1},
215                      *         {name : 'mynamespace.someevent2', onData : handler2},
216                      *     ]);
217                      * });
218                      *
219                      * @example
220                      * //Subscribe to Streaming API events.  
221                      * //The PushTopic to subscribe to must be passed in.
222                      * //The 'onComplete' method may be defined,
223                      * //and will fire when the subscription is complete.
224                      * Sfdc.canvas(function() {
225                      *     sr = JSON.parse('<%=signedRequestJson%>');
226                      *     var handler1 = function(){ console.log("onData done");},
227                      *     handler2 = function(){ console.log("onComplete done");};
228                      *     Sfdc.canvas.client.subscribe(sr.client,
229                      *         {name : 'sfdc.streamingapi', params:{topic:"/topic/InvoiceStatements"}},
230                      *          onData : handler1, onComplete : handler2}
231                      *     );
232                      * });
233                      *
234                      * 
235                      */
236                     subscribe : function(client, s) {
237                         var subs = {};
238 
239                         if ($$.isNil(s) || (!validateClient(client))) {
240                             throw "precondition fail";
241                         }
242 
243                         $$.each($$.isArray(s) ? s : [s], function (v) {
244                             if (!$$.isNil(v.name)) {
245                                 validName(v.name, ["canvas", "sfdc"]);
246 
247                                 if (v.name === STR_EVT) {
248                                     if (!$$.isNil(v.params) && !$$.isNil(v.params.topic)) {
249                                         if ($$.isNil(subscriptions[v.name])) {
250                                             subscriptions[v.name] = {};
251                                         }
252                                         subscriptions[v.name][v.params.topic] = v;
253                                     } else {
254                                         throw "[" +STR_EVT +"] topic is missing";
255                                     }
256                                 } else {
257                                     subscriptions[v.name] = v;
258                                 }
259 
260                                 subs[v.name] = {
261                                     params : v.params
262                                 };
263                             } else {
264                                 throw "subscription does not have a 'name'";
265                             }
266                         });
267                         if (!client.isVF) {
268                             postit(null, {type : "subscribe", config : {client : client}, subscriptions : subs});
269                         }
270                     },
271                     /**
272                      * @description Unsubscribes from parent or custom events.
273                      * @public
274                      * @name Sfdc.canvas.client#unsubscribe
275                      * @function
276                      * @param {client} client The object from the signed request
277                      * @param {Object} s The events to unsubscribe from
278                      * @example
279                      * //Unsubscribe from the canvas.scroll method.
280                      * Sfdc.canvas(function() {
281                      *     sr = JSON.parse('<%=signedRequestJson%>');
282                      *     Sfdc.canvas.client.unsubscribe(sr.client, "canvas.scroll");
283                      *});
284                      *
285                      * @example
286                      * //Unsubscribe from the canvas.scroll method by specifying the object name.
287                      * Sfdc.canvas(function() {
288                      *     sr = JSON.parse('<%=signedRequestJson%>');
289                      *     Sfdc.canvas.client.unsubscribe(sr.client, {name : "canvas.scroll"});
290                      *});
291                      *
292                      * @example
293                      * //Unsubscribe from multiple events.
294                      * Sfdc.canvas(function() {
295                      *     sr = JSON.parse('<%=signedRequestJson%>');
296                      *     Sfdc.canvas.client.unsubscribe(sr.client, ['canvas.scroll', 'foo.bar']);
297                      *});
298                      *
299                      * @example
300                      * //Unsubscribe from Streaming API events.
301                      * //The PushTopic to unsubscribe from  must be passed in.
302                      * Sfdc.canvas(function() {
303                      *     sr = JSON.parse('<%=signedRequestJson%>');
304                      *     Sfdc.canvas.client.unsubscribe(sr.client, {name : 'sfdc.streamingapi',
305                      *               params:{topic:"/topic/InvoiceStatements"}});
306                      *});
307                      */
308                     unsubscribe : function(client, s) {
309                         // client can pass in the handler object or just the name
310                         var subs = {};
311 
312                         if ($$.isNil(s) || !validateClient(client)) {
313                             throw "PRECONDITION FAIL: need fo supply client and event name";
314                         }
315 
316                         if ($$.isString(s)) {
317                             subs[s] = {};
318                             delete subscriptions[s];
319                         }
320                         else {
321                             $$.each($$.isArray(s) ? s : [s], function (v) {
322                                 var name = v.name ? v.name : v;
323                                 validName(name, ["canvas", "sfdc"]);
324                                 subs[name] = {
325                                     params : v.params
326                                 };
327                                 if (name === STR_EVT) {
328                                     if(!$$.isNil(subscriptions[name])) {
329                                         if (!$$.isNil(subscriptions[name][v.params.topic])) {
330                                             delete subscriptions[name][v.params.topic];
331                                         }
332                                         if ($$.size(subscriptions[name]) <= 0) {
333                                             delete subscriptions[name];
334                                         }
335                                     }
336                                 } else {
337                                     delete subscriptions[name];
338                                 }
339                             });
340                         }
341                         if (!client.isVF) {
342                             postit(null, {type : "unsubscribe", config : {client : client}, subscriptions : subs});
343                         }
344                     },
345                     /**
346                      * @description Publishes a custom event. Events are published to all subscribing canvas applications
347                      * on the same page, regardless of domain. Choose a unique namespace so the event doesn't collide with other
348                      * application events. Events can have payloads of arbitrary JSON objects.
349                      * @public
350                      * @name Sfdc.canvas.client#publish
351                      * @function
352                      * @param {client} client The object from the signed request
353                      * @param {Object} e The event to publish
354                      * @example
355                      * // Publish the foo.bar event with the specified payload.
356                      * Sfdc.canvas(function() {
357                      *     sr = JSON.parse('<%=signedRequestJson%>');
358                      *     Sfdc.canvas.client.publish(sr.client,
359                      *         {name : "foo.bar", payload : {some : 'stuff'}});
360                      *});
361                      */
362                     publish : function(client, e) {
363                         if (!$$.isNil(e) && !$$.isNil(e.name)) {
364                             validName(e.name);
365                             if (validateClient(client)) {
366                                 postit(null, {type : "publish", config : {client : client}, event : e});
367                             }
368                         }
369                     }
370                 };
371             }());
372 
373             var callback = (function() {
374                 return  {
375                     callback : function (data) {
376                         // If the server is telling us the access_token is invalid, wipe it clean.
377                         if (data.status === 401 &&
378                             $$.isArray(data.payload) &&
379                             data.payload[0].errorCode &&
380                             data.payload[0].errorCode === "INVALID_SESSION_ID") {
381                             // Session has expired logout.
382                             if ($$.oauth) {$$.oauth.logout();}
383                         }
384                         // Find appropriate client callback an invoke it.
385                         if ($$.isFunction(cbs[data.seq])) {
386                             if (!$$.isFunction(cbs[data.seq])) {
387                                 alert("not function");
388                             }
389                             cbs[data.seq](data);
390                         }
391                         else {
392                             // This can happen when the user switches out canvas apps real quick,
393                             // before the request from the last canvas app have finish processing.
394                             // We will ignore any of these results as the canvas app is no longer active to
395                             // respond to the results.
396                         }
397                     }
398                 };
399             }());
400 
401             var services = (function() {
402 
403                 var sr;
404 
405                 return  {
406                     /**
407                      * @description Performs a cross-domain, asynchronous HTTP request.
408                      <br>Note: this method shouldn't be used for same domain requests.
409                      * @param {String} url The URL to which the request is sent
410                      * @param {Object} settings A set of key/value pairs to configure the request
411                      <br>The success setting is required at minimum and should be a callback function
412                      * @config {String} [client] The required client context {oauthToken: "", targetOrigin : "", instanceId : ""}
413                      * @config {String} [contentType] "application/json"
414                      * @config {String} [data] The request body
415                      * @config {String} [headers] request headers
416                      * @config {String} [method="GET"] The type of AJAX request to make
417                      * @config {Function} [success] Callback for all responses from the server (failure and success). Signature: success(response); interesting fields: [response.data, response.responseHeaders, response.status, response.statusText}
418                      * @config {Boolean} [async=true] Asynchronous: only <code>true</code> is supported.
419                      * @name Sfdc.canvas.client#ajax
420                      * @function
421                      * @throws An error if the URL is missing or the settings object doesn't contain a success callback function.
422                      * @example
423                      * //Posting to a Chatter feed:
424                      * var sr = JSON.parse('<%=signedRequestJson%>');
425                      * var url = sr.context.links.chatterFeedsUrl+"/news/"
426                      *                                   +sr.context.user.userId+"/feed-items";
427                      * var body = {body : {messageSegments : [{type: "Text", text: "Some Chatter Post"}]}};
428                      * Sfdc.canvas.client.ajax(url,
429                      *   {client : sr.client,
430                      *     method: 'POST',
431                      *     contentType: "application/json",
432                      *     data: JSON.stringify(body),
433                      *     success : function(data) {
434                      *     if (201 === data.status) {
435                      *          alert("Success"
436                      *          }
437                      *     }
438                      *   });
439                      * @example
440                      * // Gets a list of Chatter users:
441                      * // Paste the signed request string into a JavaScript object for easy access.
442                      * var sr = JSON.parse('<%=signedRequestJson%>');
443                      * // Reference the Chatter user's URL from Context.Links object.
444                      * var chatterUsersUrl = sr.context.links.chatterUsersUrl;
445                      *
446                      * // Make an XHR call back to Salesforce through the supplied browser proxy.
447                      * Sfdc.canvas.client.ajax(chatterUsersUrl,
448                      *   {client : sr.client,
449                      *   success : function(data){
450                      *   // Make sure the status code is OK.
451                      *   if (data.status === 200) {
452                      *     // Alert with how many Chatter users were returned.
453                      *     alert("Got back "  + data.payload.users.length +
454                      *     " users"); // Returned 2 users
455                      *    }
456                      * })};
457                      */
458                     ajax : function (url, settings) {
459 
460                         var ccb, config, defaults;
461 
462                         if (!url) {
463                             throw "PRECONDITION ERROR: url required with AJAX call";
464                         }
465                         if (!settings || !$$.isFunction(settings.success)) {
466                             throw "PRECONDITION ERROR: function: 'settings.success' missing.";
467                         }
468                         if (! validateClient(settings.client, settings.success)) {
469                             return;
470                         }
471 
472                         ccb = settings.success;
473                         defaults = {
474                             method: 'GET',
475                             async: true,
476                             contentType: "application/json",
477                             headers: {"Authorization" : "OAuth "  + settings.client.oauthToken,
478                                 "Accept" : "application/json"},
479                             data: null
480                         };
481                         config = $$.extend(defaults, settings || {});
482 
483                         // Remove any listeners as functions cannot get marshaled.
484                         config.success = undefined;
485                         config.failure = undefined;
486                         // Don't allow the client to set "*" as the target origin.
487                         if (config.client.targetOrigin === "*") {
488                             config.client.targetOrigin = null;
489                         }
490                         else {
491                             // We need to set this here so we can validate the origin when we receive the call back
492                             purl = $$.startsWithHttp(config.targetOrigin, purl);
493                         }
494                         postit(ccb, {type : "ajax", url : url, config : config});
495                     },
496                     /**
497                      * @description Returns the context for the current user and organization.
498                      * @public
499                      * @name Sfdc.canvas.client#ctx
500                      * @function
501                      * @param {Function} clientscb The callback function to run when the call to ctx completes
502                      * @param {Object} client The signedRequest.client.
503                      * @example
504                      * // Gets context in the canvas app.
505                      *
506                      * function callback(msg) {
507                      *   if (msg.status !== 200) {
508                      *     alert("Error: " + msg.status);
509                      *     return;
510                      *   }
511                      *   alert("Payload: ", msg.payload);
512                      * }
513                      * var ctxlink = Sfdc.canvas.byId("ctxlink");
514                      * var client = Sfdc.canvas.oauth.client();
515                      * ctxlink.onclick=function() {
516                      *   Sfdc.canvas.client.ctx(callback, client)};
517                      * }
518                      */
519                     ctx : function (clientscb, client) {
520                         if (validateClient(client, clientscb)) {
521                             postit(clientscb, {type : "ctx", accessToken : client.oauthToken, config : {client : client}});
522                         }
523                     },
524                     /**
525                      * @public
526                      * @function
527                      * @name Sfdc.canvas.client#token
528                      * @description Stores or gets the oauth token in a local javascript variable. Note, if longer term
529                      * (survive page refresh) storage is needed store the oauth token on the server side.
530                      * @param {String} t oauth token, if supplied it will be stored in a volatile local JS variable.
531                      * @returns {Object} the oauth token.
532                      */
533                     token : function(t) {
534                         return $$.oauth && $$.oauth.token(t);
535                     },
536                     /**
537                      * @description Returns the current version of the client.
538                      * @public
539                      * @function
540                      * @name Sfdc.canvas.client#version
541                      * @returns {Object} {clientVersion : "29.0", parentVersion : "29.0"}.
542                      */
543                     version : function() {
544                         return {clientVersion: cversion, parentVersion : pversion};
545                     },
546                     /**
547                      * @function
548                      * @public
549                      * @name Sfdc.canvas.client#signedrequest
550                      * @description Temporary storage for the signed request. An alternative for users storing SR in
551                      * a global variable. Note: if you would like a new signed request take a look at refreshSignedRequest().
552                      * @param {Object} s signedrequest to be temporarily stored in Sfdc.canvas.client object.
553                      * @returns {Object} the value previously stored
554                      */
555                     signedrequest : function(s) {
556                         if (arguments.length > 0) {
557                             sr = s;
558                         }
559                         return sr;
560                     },
561                     /**
562                      * @function
563                      * @public
564                      * @name Sfdc.canvas.client#refreshSignedRequest
565                      * @description Refresh the signed request. Obtain a new signed request on demand. Note the
566                      * authentication mechanism of the canvas app must be set to SignedRequest and not OAuth.
567                      * @param {Function} clientscb The client's callback function to receive the base64 encoded signed request.
568                      * @example
569                      * // Gets a signed request on demand.
570                      *  Sfdc.canvas.client.refreshSignedRequest(function(data) {
571                      *      if (data.status === 200) {
572                      *          var signedRequest =  data.payload.response;
573                      *          var part = signedRequest.split('.')[1];
574                      *          var obj = JSON.parse(Sfdc.canvas.decode(part));
575                      *      }
576                      *   }
577                      */
578                     refreshSignedRequest : function(clientscb) { 
579                         var id = window.name.substring("canvas-frame-".length),
580                             client = {oauthToken : "null", instanceId : id, targetOrigin : "*"};
581                         postit(clientscb, {type : "refresh", accessToken : client.oauthToken, config : {client : client}});
582                     },
583                     /**
584                      * @public
585                      * @function
586                      * @name Sfdc.canvas.client#repost
587                      * @description Repost the signed request. Instruct the parent window to initiate a new post to the
588                      * canvas url. Note the authentication mechanism of the canvas app must be set to SignedRequest and not OAuth.
589                      * @param {Boolean}[refresh=false] Refreshes the signed request when set to true.
590                      * @example
591                      * // Gets a signed request on demand, without refreshing the signed request.
592                      *  Sfdc.canvas.client.repost();
593                      * // Gets a signed request on demand, first by refreshing the signed request.
594                      *  Sfdc.canvas.client.repost({refresh : true});
595                      */
596                     repost : function(refresh) {
597                         var id = window.name.substring("canvas-frame-".length),
598                             client = {oauthToken : "null", instanceId : id, targetOrigin : "*"},
599                             r= refresh || false;
600                         postit(null, {type : "repost", accessToken : client.oauthToken, config : {client : client}, refresh : r});
601                     }
602 
603                 };
604             }());
605 
606             var frame = (function() {
607                 return  {
608                     /**
609                      * @public
610                      * @name Sfdc.canvas.client#size
611                      * @function
612                      * @description Returns the current size of the iFrame.
613                      * @return {Object}<br>
614                      * <code>heights.contentHeight</code>: the height of the virtual iFrame, all content, not just visible content.<br>
615                      * <code>heights.pageHeight</code>: the height of the visible iFrame in the browser.<br>
616                      * <code>heights.scrollTop</code>: the position of the scroll bar measured from the top.<br>
617                      * <code>widths.contentWidth</code>: the width of the virtual iFrame, all content, not just visible content.<br>
618                      * <code>widths.pageWidth</code>: the width of the visible iFrame in the browser.<br>
619                      * <code>widths.scrollLeft</code>: the position of the scroll bar measured from the left.
620                      * @example
621                      * //get the size of the iFrame and print out each component.
622                      * var sizes = Sfdc.canvas.client.size();
623                      * console.log("contentHeight; " + sizes.heights.contentHeight);
624                      * console.log("pageHeight; " + sizes.heights.pageHeight);
625                      * console.log("scrollTop; " + sizes.heights.scrollTop);
626                      * console.log("contentWidth; " + sizes.widths.contentWidth);
627                      * console.log("pageWidth; " + sizes.widths.pageWidth);
628                      * console.log("scrollLeft; " + sizes.widths.scrollLeft);
629                      */
630                     size : function() {
631                         var docElement = $$.document().documentElement;
632                         var contentHeight = docElement.scrollHeight,
633                             pageHeight = docElement.clientHeight,
634                             scrollTop = (docElement && docElement.scrollTop) || $$.document().body.scrollTop,
635                             contentWidth = docElement.scrollWidth,
636                             pageWidth = docElement.clientWidth,
637                             scrollLeft = (docElement && docElement.scrollLeft) || $$.document().body.scrollLeft;
638 
639                         return {heights : {contentHeight : contentHeight, pageHeight : pageHeight, scrollTop : scrollTop},
640                             widths : {contentWidth : contentWidth, pageWidth : pageWidth, scrollLeft : scrollLeft}};
641                     },
642                     /**
643                      * @public
644                      * @name Sfdc.canvas.client#resize
645                      * @function
646                      * @description Informs the parent window to resize the canvas iFrame. If no parameters are specified,
647                      * the parent window attempts to determine the height of the canvas app based on the
648                      * content and then sets the iFrame width and height accordingly. To explicitly set the dimensions,
649                      * pass in an object with height and/or width properties.
650                      * @param {Client} client The object from the signed request
651                      * @param {size} size The optional height and width information
652                      * @example
653                      * //Automatically determine the size
654                      * Sfdc.canvas(function() {
655                      *     sr = JSON.parse('<%=signedRequestJson%>');
656                      *     Sfdc.canvas.client.resize(sr.client);
657                      * });
658                      *
659                      * @example
660                      * //Set the height and width explicitly
661                      * Sfdc.canvas(function() {
662                      *     sr = JSON.parse('<%=signedRequestJson%>');
663                      *     Sfdc.canvas.client.resize(sr.client, {height : "1000px", width : "900px"});
664                      * });
665                      *
666                      * @example
667                      * //Set only the height
668                      * Sfdc.canvas(function() {
669                      *     sr = JSON.parse('<%=signedRequestJson%>');
670                      *     Sfdc.canvas.client.resize(sr.client, {height : "1000px"});
671                      * });
672                      *
673                      */
674                     resize : function(client, size) {
675                         var sh, ch, sw, cw, s = {height : "", width : ""},
676                             docElement = $$.document().documentElement;
677 
678                         // If the size was not supplied, adjust window
679                         if ($$.isNil(size)) {
680                             sh = docElement.scrollHeight;
681                             ch = docElement.clientHeight;
682                             if (ch !== sh) {
683                                 s.height = sh + "px";
684                             }
685                             sw = docElement.scrollWidth;
686                             cw = docElement.clientWidth;
687                             if (sw !== cw) {
688                                 s.width = sw + "px";
689                             }
690                         }
691                         else {
692                             if (!$$.isNil(size.height)) {
693                                 s.height = size.height;
694                             }
695                             if (!$$.isNil(size.width)) {
696                                 s.width = size.width;
697                             }
698                         }
699                         if (!$$.isNil(s.height) || !$$.isNil(s.width)) {
700                             postit(null, {type : "resize", config : {client : client}, size : s});
701                         }
702                     },
703                     /**
704                      * @public
705                      * @name Sfdc.canvas.client#autogrow
706                      * @function
707                      * @description Starts or stops a timer which checks the content size of the iFrame and
708                      * adjusts the frame accordingly.
709                      * Use this function when you know your content is changing size, but you're not sure when. There's a delay as
710                      * the resizing is done asynchronously. Therfore, if you know when your content changes size, you should 
711                      * explicitly call the resize() method and save browser CPU cycles.
712                      * Note: you should turn off scrolling before this call, otherwise you might get a flicker.
713                      * @param {client} client The object from the signed request
714                      * @param {boolean} b Whether it's turned on or off; defaults to <code>true</code>
715                      * @param {Integer} interval The interval used to check content size; default timeout is 300ms.
716                      * @example
717                      *
718                      * // Turn on auto grow with default settings.
719                      * Sfdc.canvas(function() {
720                      *     sr = JSON.parse('<%=signedRequestJson%>');
721                      *     Sfdc.canvas.client.autogrow(sr.client);
722                      * });
723                      *
724                      * // Turn on auto grow with a polling interval of 100ms (milliseconds).
725                      * Sfdc.canvas(function() {
726                      *     sr = JSON.parse('<%=signedRequestJson%>');
727                      *     Sfdc.canvas.client.autogrow(sr.client, true, 100);
728                      * });
729                      *
730                      * // Turn off auto grow.
731                      * Sfdc.canvas(function() {
732                      *     sr = JSON.parse('<%=signedRequestJson%>');
733                      *     Sfdc.canvas.client.autogrow(sr.client, false);
734                      * });
735                      */
736                     autogrow : function(client, b, interval) {
737                         var ival = ($$.isNil(interval)) ? 300 : interval;
738                         autog  = ($$.isNil(b)) ? true : b;
739                         if (autog === false) {
740                             return;
741                         }
742                         setTimeout(function () {
743                             submodules.frame.resize(client);
744                             submodules.frame.autogrow(client, autog);
745                         },ival);
746                     }
747                 };
748             }());
749 
750             return {
751                 services : services,
752                 frame : frame,
753                 event : event,
754                 callback : callback
755             };
756         }());
757 
758         $$.xd.receive(xdCallback, getTargetOrigin);
759 
760         return {
761             ctx : submodules.services.ctx,
762             ajax : submodules.services.ajax,
763             token : submodules.services.token,
764             version : submodules.services.version,
765             resize : submodules.frame.resize,
766             size : submodules.frame.size,
767             autogrow : submodules.frame.autogrow,
768             subscribe : submodules.event.subscribe,
769             unsubscribe : submodules.event.unsubscribe,
770             publish : submodules.event.publish,
771             signedrequest : submodules.services.signedrequest,
772             refreshSignedRequest : submodules.services.refreshSignedRequest,
773             repost : submodules.services.repost
774         };
775     }());
776 
777     $$.module('Sfdc.canvas.client', module);
778 
779 }(Sfdc.canvas));