1 /**
  2 * Copyright (c) 2011, 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  *@namespace Sfdc.canvas.client
 28  *@name Sfdc.canvas.client
 29  */
 30 (function ($$) {
 31 
 32     "use strict";
 33 
 34     var pversion, cversion = "29.0";
 35 
 36     var module =   (function() /**@lends module */ {
 37 
 38         var purl;
 39 
 40         function startsWithHttp(u, d) {
 41             return  $$.isNil(u) ? u : (u.substring(0, 4) === "http") ? u : d;
 42         }
 43 
 44         // returns the url of the Parent Window
 45         function getTargetOrigin(to) {
 46             var h;
 47             if (to === "*") {return to;}
 48             if (!$$.isNil(to)) {
 49                 h = $$.stripUrl(to);
 50                 purl = startsWithHttp(h, purl);
 51                 if (purl) {return purl;}
 52             }
 53             // This relies on the parent passing it in. This may not be there as the client can do a redirect.
 54             h = $$.document().location.hash;
 55             if (h) {
 56                 h = decodeURIComponent(h.replace(/^#/, ''));
 57                 purl = startsWithHttp(h, purl);
 58             }
 59             return purl;
 60         }
 61 
 62         // The main cross domain callback handler
 63         function xdCallback(data) {
 64             if (data) {
 65                 if (submodules[data.type]) {
 66                     submodules[data.type].callback(data);
 67                 }
 68                 // Just ignore...
 69             }
 70         }
 71 
 72         var submodules = (function () {
 73 
 74             var cbs = [], seq = 0, autog = true;
 75 
 76             // Functions common to submodules...
 77 
 78             function postit(clientscb, message) {
 79                 var wrapped, to, c;
 80 
 81                 // need to keep a mapping from request to callback, otherwise
 82                 // wrong callbacks get called. Unfortunately, this is the only
 83                 // way to handle this as postMessage acts more like topic/queue.
 84                 // limit the sequencers to 100 avoid out of memory errors
 85                 seq = (seq > 100) ? 0 : seq + 1;
 86                 cbs[seq] = clientscb;
 87                 wrapped = {seq : seq, src : "client", clientVersion : cversion, parentVersion: pversion, body : message};
 88 
 89                 c  = message && message.config && message.config.client;
 90                 to = getTargetOrigin($$.isNil(c) ? null : c.targetOrigin);
 91                 if ($$.isNil(to)) {
 92                     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.";
 93                 }
 94                 $$.xd.post(wrapped, to, parent);
 95             }
 96 
 97             function validateClient(client, cb) {
 98                 var msg;
 99 
100                 client = client || $$.oauth && $$.oauth.client();
101 
102                 if ($$.isNil(client) || $$.isNil(client.oauthToken)) {
103                     msg = {status : 401, statusText : "Unauthorized" , parentVersion : pversion, payload : "client or client.oauthToken not supplied"};
104                 }
105                 if ($$.isNil(client.instanceId) || $$.isNil(client.targetOrigin)) {
106                     msg = {status : 400, statusText : "Bad Request" , parentVersion : pversion, payload : "client.instanceId or client.targetOrigin not supplied"};
107                 }
108                 if (!$$.isNil(msg)) {
109                     if ($$.isFunction(cb)) {
110                         cb(msg);
111                         return false;
112                     }
113                     else {
114                         throw msg;
115                     }
116                 }
117                 return true;
118             }
119 
120             // Submodules...
121 
122             var event = (function() {
123                 var subscriptions = {}, STR_EVT = "sfdc.streamingapi";
124 
125                 function validName(name, res) {
126                     var msg, r = $$.validEventName(name, res);
127                     if (r !== 0) {
128                         msg = {1 : "Event names can only contain one namespace",
129                                2 : "Namespace has already been reserved",
130                                3 : "Event name contains invalid characters"
131                         };
132                         throw msg[r];
133                     }
134                 }
135 
136                 function findSubscription(event) {
137                     var s, name = event.name;
138 
139                     if (name === STR_EVT) {
140                         s = subscriptions[name][event.params.topic];
141                     } else {
142                         s = subscriptions[name];
143                     }
144 
145                     if (!$$.isNil(s) && $$.isFunction(s.onData)) {
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                      * @description Stores or gets the oauth token in a local javascript variable. Note, if longer term
526                      * (survive page refresh) storage is needed store the oauth token on the server side.
527                      * @param {String} t oauth token, if supplied it will be stored in a volatile local JS variable.
528                      * @returns {Object} the oauth token.
529                      */
530                     token : function(t) {
531                         return $$.oauth && $$.oauth.token(t);
532                     },
533                     /**
534                      * @description Returns the current version of the client.
535                      * @returns {Object} {clientVersion : "29.0", parentVersion : "29.0"}.
536                      */
537                     version : function() {
538                         return {clientVersion: cversion, parentVersion : pversion};
539                     },
540                     /**
541                      * @description Temporary storage for the signed request. An alternative for users storing SR in
542                      * a global variable.
543                      * @param {Object} s signedrequest to be temporarily stored in Sfdc.canvas.client object.
544                      * @returns {Object} the value previously stored
545                      */
546                     signedrequest : function(s) {
547                         if (arguments.length > 0) {
548                             sr = s;
549                         }
550                         return sr;
551                     }
552                 };
553             }());
554 
555             var frame = (function() {
556                 return  {
557                     /**
558                      * @public
559                      * @name Sfdc.canvas.client#size
560                      * @function
561                      * @description Returns the current size of the iFrame.
562                      * @return {Object}<br>
563                      * <code>heights.contentHeight</code>: the height of the virtual iFrame, all content, not just visible content.<br>
564                      * <code>heights.pageHeight</code>: the height of the visible iFrame in the browser.<br>
565                      * <code>heights.scrollTop</code>: the position of the scroll bar measured from the top.<br>
566                      * <code>widths.contentWidth</code>: the width of the virtual iFrame, all content, not just visible content.<br>
567                      * <code>widths.pageWidth</code>: the width of the visible iFrame in the browser.<br>
568                      * <code>widths.scrollLeft</code>: the position of the scroll bar measured from the left.
569                      * @example
570                      * //get the size of the iFrame and print out each component.
571                      * var sizes = Sfdc.canvas.client.size();
572                      * console.log("contentHeight; " + sizes.heights.contentHeight);
573                      * console.log("pageHeight; " + sizes.heights.pageHeight);
574                      * console.log("scrollTop; " + sizes.heights.scrollTop);
575                      * console.log("contentWidth; " + sizes.widths.contentWidth);
576                      * console.log("pageWidth; " + sizes.widths.pageWidth);
577                      * console.log("scrollLeft; " + sizes.widths.scrollLeft);
578                      */
579                     size : function() {
580                         var docElement = $$.document().documentElement;
581                         var contentHeight = docElement.scrollHeight,
582                             pageHeight = docElement.clientHeight,
583                             scrollTop = (docElement && docElement.scrollTop) || $$.document().body.scrollTop,
584                             contentWidth = docElement.scrollWidth,
585                             pageWidth = docElement.clientWidth,
586                             scrollLeft = (docElement && docElement.scrollLeft) || $$.document().body.scrollLeft;
587 
588                         return {heights : {contentHeight : contentHeight, pageHeight : pageHeight, scrollTop : scrollTop},
589                             widths : {contentWidth : contentWidth, pageWidth : pageWidth, scrollLeft : scrollLeft}};
590                     },
591                     /**
592                      * @public
593                      * @name Sfdc.canvas.client#resize
594                      * @function
595                      * @description Informs the parent window to resize the canvas iFrame. If no parameters are specified,
596                      * the parent window attempts to determine the height of the canvas app based on the
597                      * content and then sets the iFrame width and height accordingly. To explicitly set the dimensions,
598                      * pass in an object with height and/or width properties.
599                      * @param {Client} client The object from the signed request
600                      * @param {size} size The optional height and width information
601                      * @example
602                      * //Automatically determine the size
603                      * Sfdc.canvas(function() {
604                      *     sr = JSON.parse('<%=signedRequestJson%>');
605                      *     Sfdc.canvas.client.resize(sr.client);
606                      * });
607                      *
608                      * @example
609                      * //Set the height and width explicitly
610                      * Sfdc.canvas(function() {
611                      *     sr = JSON.parse('<%=signedRequestJson%>');
612                      *     Sfdc.canvas.client.resize(sr.client, {height : "1000px", width : "900px"});
613                      * });
614                      *
615                      * @example
616                      * //Set only the height
617                      * Sfdc.canvas(function() {
618                      *     sr = JSON.parse('<%=signedRequestJson%>');
619                      *     Sfdc.canvas.client.resize(sr.client, {height : "1000px"});
620                      * });
621                      *
622                      */
623                     resize : function(client, size) {
624                         var sh, ch, sw, cw, s = {height : "", width : ""},
625                             docElement = $$.document().documentElement;
626 
627                         // If the size was not supplied, adjust window
628                         if ($$.isNil(size)) {
629                             sh = docElement.scrollHeight;
630                             ch = docElement.clientHeight;
631                             if (ch !== sh) {
632                                 s.height = sh + "px";
633                             }
634                             sw = docElement.scrollWidth;
635                             cw = docElement.clientWidth;
636                             if (sw !== cw) {
637                                 s.width = sw + "px";
638                             }
639                         }
640                         else {
641                             if (!$$.isNil(size.height)) {
642                                 s.height = size.height;
643                             }
644                             if (!$$.isNil(size.width)) {
645                                 s.width = size.width;
646                             }
647                         }
648                         if (!$$.isNil(s.height) || !$$.isNil(s.width)) {
649                             postit(null, {type : "resize", config : {client : client}, size : s});
650                         }
651                     },
652                     /**
653                      * @public
654                      * @name Sfdc.canvas.client#autogrow
655                      * @function
656                      * @description Starts or stops a timer which checks the content size of the iFrame and
657                      * adjusts the frame accordingly.
658                      * Use this function when you know your content is changing size, but you're not sure when. There's a delay as
659                      * the resizing is done asynchronously. Therfore, if you know when your content changes size, you should 
660                      * explicitly call the resize() method and save browser CPU cycles.
661                      * Note: you should turn off scrolling before this call, otherwise you might get a flicker.
662                      * @param {client} client The object from the signed request
663                      * @param {boolean} b Whether it's turned on or off; defaults to <code>true</code>
664                      * @param {Integer} interval The interval used to check content size; default timeout is 300ms.
665                      * @example
666                      *
667                      * // Turn on auto grow with default settings.
668                      * Sfdc.canvas(function() {
669                      *     sr = JSON.parse('<%=signedRequestJson%>');
670                      *     Sfdc.canvas.client.autogrow(sr.client);
671                      * });
672                      *
673                      * // Turn on auto grow with a polling interval of 100ms (milliseconds).
674                      * Sfdc.canvas(function() {
675                      *     sr = JSON.parse('<%=signedRequestJson%>');
676                      *     Sfdc.canvas.client.autogrow(sr.client, true, 100);
677                      * });
678                      *
679                      * // Turn off auto grow.
680                      * Sfdc.canvas(function() {
681                      *     sr = JSON.parse('<%=signedRequestJson%>');
682                      *     Sfdc.canvas.client.autogrow(sr.client, false);
683                      * });
684                      */
685                     autogrow : function(client, b, interval) {
686                         var ival = ($$.isNil(interval)) ? 300 : interval;
687                         autog  = ($$.isNil(b)) ? true : b;
688                         if (autog === false) {
689                             return;
690                         }
691                         setTimeout(function () {
692                             submodules.frame.resize(client);
693                             submodules.frame.autogrow(client, autog);
694                         },ival);
695                     }
696                 };
697             }());
698 
699             return {
700                 services : services,
701                 frame : frame,
702                 event : event,
703                 callback : callback
704             };
705         }());
706 
707         $$.xd.receive(xdCallback, getTargetOrigin);
708 
709         return {
710             ctx : submodules.services.ctx,
711             ajax : submodules.services.ajax,
712             token : submodules.services.token,
713             version : submodules.services.version,
714             resize : submodules.frame.resize,
715             size : submodules.frame.size,
716             autogrow : submodules.frame.autogrow,
717             subscribe : submodules.event.subscribe,
718             unsubscribe : submodules.event.unsubscribe,
719             publish : submodules.event.publish,
720             signedrequest : submodules.services.signedrequest
721         };
722     }());
723 
724     $$.module('Sfdc.canvas.client', module);
725 
726 }(Sfdc.canvas));
727