selenium-webdriver 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. data/chrome/prebuilt/Win32/Release/npchromedriver.dll +0 -0
  2. data/chrome/prebuilt/x64/Release/npchromedriver.dll +0 -0
  3. data/chrome/src/extension/background.js +64 -48
  4. data/chrome/src/extension/content_script.js +253 -132
  5. data/chrome/src/extension/manifest-nonwin.json +1 -1
  6. data/chrome/src/extension/manifest-win.json +1 -1
  7. data/chrome/src/extension/utils.js +8 -8
  8. data/chrome/src/rb/lib/selenium/webdriver/chrome.rb +9 -0
  9. data/chrome/src/rb/lib/selenium/webdriver/chrome/bridge.rb +38 -280
  10. data/chrome/src/rb/lib/selenium/webdriver/chrome/command_executor.rb +119 -117
  11. data/chrome/src/rb/lib/selenium/webdriver/chrome/launcher.rb +36 -26
  12. data/common/src/js/abstractcommandprocessor.js +9 -11
  13. data/common/src/js/command.js +159 -83
  14. data/common/src/js/core/RemoteRunner.html +2 -2
  15. data/common/src/js/core/TestRunner-splash.html +3 -3
  16. data/common/src/js/core/TestRunner.html +5 -17
  17. data/common/src/js/core/scripts/htmlutils.js +4208 -2506
  18. data/common/src/js/core/scripts/selenium-api.js +2 -2
  19. data/common/src/js/core/scripts/selenium-browserbot.js +66 -58
  20. data/common/src/js/core/scripts/selenium-version.js +1 -1
  21. data/common/src/js/localcommandprocessor.js +5 -19
  22. data/common/src/js/testcase.js +2 -0
  23. data/common/src/js/webdriver.js +63 -93
  24. data/common/src/js/webelement.js +40 -42
  25. data/common/src/rb/lib/selenium/webdriver.rb +23 -14
  26. data/common/src/rb/lib/selenium/webdriver/bridge_helper.rb +8 -35
  27. data/common/src/rb/lib/selenium/webdriver/child_process.rb +2 -0
  28. data/common/src/rb/lib/selenium/webdriver/core_ext/dir.rb +1 -0
  29. data/common/src/rb/lib/selenium/webdriver/core_ext/string.rb +5 -0
  30. data/common/src/rb/lib/selenium/webdriver/driver.rb +20 -15
  31. data/common/src/rb/lib/selenium/webdriver/driver_extensions/takes_screenshot.rb +7 -2
  32. data/common/src/rb/lib/selenium/webdriver/element.rb +11 -2
  33. data/common/src/rb/lib/selenium/webdriver/error.rb +9 -5
  34. data/common/src/rb/lib/selenium/webdriver/keys.rb +1 -2
  35. data/common/src/rb/lib/selenium/webdriver/navigation.rb +16 -0
  36. data/common/src/rb/lib/selenium/webdriver/options.rb +32 -0
  37. data/common/src/rb/lib/selenium/webdriver/platform.rb +17 -1
  38. data/firefox/prebuilt/Win32/Release/webdriver-firefox.dll +0 -0
  39. data/firefox/src/extension/components/dispatcher.js +492 -0
  40. data/firefox/src/extension/components/driver-component.js +4 -1
  41. data/firefox/src/extension/components/errorcode.js +70 -0
  42. data/firefox/src/extension/components/firefoxDriver.js +173 -154
  43. data/firefox/src/extension/components/nsCommandProcessor.js +171 -132
  44. data/firefox/src/extension/components/promptService.js +5 -5
  45. data/firefox/src/extension/components/request.js +219 -0
  46. data/firefox/src/extension/components/response.js +276 -0
  47. data/firefox/src/extension/components/session.js +281 -0
  48. data/firefox/src/extension/components/sessionstore.js +226 -0
  49. data/firefox/src/extension/components/socketListener.js +350 -100
  50. data/firefox/src/extension/components/utils.js +166 -98
  51. data/firefox/src/extension/components/webdriverserver.js +9 -5
  52. data/firefox/src/extension/components/wrappedElement.js +189 -166
  53. data/firefox/src/extension/install.rdf +1 -1
  54. data/firefox/src/rb/lib/selenium/webdriver/firefox.rb +2 -0
  55. data/firefox/src/rb/lib/selenium/webdriver/firefox/binary.rb +39 -33
  56. data/firefox/src/rb/lib/selenium/webdriver/firefox/bridge.rb +7 -421
  57. data/firefox/src/rb/lib/selenium/webdriver/firefox/extension_connection.rb +7 -64
  58. data/firefox/src/rb/lib/selenium/webdriver/firefox/launcher.rb +2 -3
  59. data/firefox/src/rb/lib/selenium/webdriver/firefox/profile.rb +54 -10
  60. data/firefox/src/rb/lib/selenium/webdriver/firefox/profiles_ini.rb +2 -0
  61. data/firefox/src/rb/lib/selenium/webdriver/firefox/util.rb +6 -0
  62. data/jobbie/prebuilt/Win32/Release/InternetExplorerDriver.dll +0 -0
  63. data/jobbie/prebuilt/x64/Release/InternetExplorerDriver.dll +0 -0
  64. data/jobbie/src/rb/lib/selenium/webdriver/ie.rb +2 -0
  65. data/jobbie/src/rb/lib/selenium/webdriver/ie/bridge.rb +38 -13
  66. data/jobbie/src/rb/lib/selenium/webdriver/ie/lib.rb +9 -2
  67. data/jobbie/src/rb/lib/selenium/webdriver/ie/util.rb +5 -0
  68. data/remote/client/src/rb/lib/selenium/webdriver/remote.rb +2 -0
  69. data/remote/client/src/rb/lib/selenium/webdriver/remote/bridge.rb +42 -38
  70. data/remote/client/src/rb/lib/selenium/webdriver/remote/commands.rb +56 -47
  71. data/remote/client/src/rb/lib/selenium/webdriver/remote/default_http_client.rb +26 -26
  72. data/remote/client/src/rb/lib/selenium/webdriver/remote/patron_http_client.rb +58 -0
  73. data/remote/client/src/rb/lib/selenium/webdriver/remote/response.rb +10 -12
  74. data/remote/client/src/rb/lib/selenium/webdriver/remote/server_error.rb +2 -17
  75. metadata +44 -23
  76. data/common/src/js/context.js +0 -58
  77. data/firefox/src/extension/components/context.js +0 -37
@@ -0,0 +1,492 @@
1
+ /*
2
+ Copyright 2007-2010 WebDriver committers
3
+ Copyright 2007-2010 Google 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
+ /**
20
+ * Dispatches commands received by the WebDriver server.
21
+ * @constructor
22
+ */
23
+ function Dispatcher() {
24
+ this.resources_ = [];
25
+ this.init_();
26
+ }
27
+
28
+
29
+ /**
30
+ * Utility function used to respond to a command that is recognised, but not
31
+ * implemented. Returns a 501.
32
+ * @param {Request} The request to respond to.
33
+ * @param {Response} Class used to send the response.
34
+ */
35
+ Dispatcher.notImplemented = function(request, response) {
36
+ response.sendError(Response.NOT_IMPLEMENTED, 'Unsupported command',
37
+ 'text/plain');
38
+ };
39
+
40
+
41
+ /**
42
+ * Returns a function that translates a WebDriver HTTP request to a legacy
43
+ * command.
44
+ * @param {string} name The legacy command name.
45
+ * @return {function(Request, Response)} The translation function.
46
+ * @private
47
+ */
48
+ Dispatcher.executeAs = function(name) {
49
+ return function(request, response) {
50
+ var json = {
51
+ 'name': name,
52
+ 'sessionId': {
53
+ 'value': request.getAttribute('sessionId')
54
+ },
55
+ 'parameters': JSON.parse(request.getBody() || '{}')
56
+ };
57
+
58
+ // All request attributes, excluding sessionId and parameters also passed
59
+ // the body payload, should be added to the parameters.
60
+ var attributeNames = request.getAttributeNames();
61
+ for (var attrName; attrName = attributeNames.shift();) {
62
+ if (attrName != 'sessionId' && !json['parameters'][attrName]) {
63
+ json['parameters'][attrName] = request.getAttribute(attrName);
64
+ }
65
+ }
66
+
67
+ var jsonString = JSON.stringify(json);
68
+ var callback = function(jsonResponseString) {
69
+ var jsonResponse = JSON.parse(jsonResponseString);
70
+ // Going to need more granularity here I think.
71
+ if (jsonResponse.status != ErrorCode.SUCCESS) {
72
+ response.setStatus(Response.INTERNAL_ERROR);
73
+ }
74
+
75
+ response.setContentType('application/json');
76
+ response.setBody(jsonResponseString);
77
+ response.commit();
78
+ };
79
+
80
+ // Dispatch the command.
81
+ Components.classes['@googlecode.com/webdriver/command-processor;1'].
82
+ getService(Components.interfaces.nsICommandProcessor).
83
+ execute(jsonString, callback);
84
+ };
85
+ };
86
+
87
+
88
+ /**
89
+ * Creates a special handler for translating a request for a new session to a
90
+ * request understood by the legacy nsICommandProcessor.
91
+ */
92
+ Dispatcher.translateNewSession = function() {
93
+ return function(request, response) {
94
+ var callback = function(jsonResponseString) {
95
+ var jsonResponse = JSON.parse(jsonResponseString);
96
+ // Going to need more granularity here I think.
97
+ if (jsonResponse.status != 0) {
98
+ response.sendError(Response.INTERNAL_ERROR,
99
+ jsonResponseString, 'application/json');
100
+ } else {
101
+ var url = request.getRequestUrl();
102
+ response.setStatus(Response.SEE_OTHER);
103
+ response.setHeader('Location',
104
+ url.scheme + '://' + url.hostPort + url.path + '/' +
105
+ jsonResponse.value);
106
+ response.commit();
107
+ }
108
+ };
109
+
110
+ // Dispatch the command.
111
+ Components.classes['@googlecode.com/webdriver/command-processor;1'].
112
+ getService(Components.interfaces.nsICommandProcessor).
113
+ execute('{"name":"newSession"}', callback);
114
+ };
115
+ };
116
+
117
+
118
+ /**
119
+ * Initializes the command bindings for this dispatcher.
120
+ * @private
121
+ */
122
+ Dispatcher.prototype.init_ = function() {
123
+ this.bind_('/config/drivers'). // Recognised, but not supported.
124
+ on(Request.Method.POST, Dispatcher.notImplemented);
125
+
126
+ this.bind_('/session').
127
+ on(Request.Method.POST, Dispatcher.translateNewSession());
128
+
129
+ this.bind_('/session/:sessionId').
130
+ on(Request.Method.GET, Dispatcher.executeAs('getSessionCapabilities')).
131
+ on(Request.Method.DELETE, Dispatcher.executeAs('quit'));
132
+
133
+ this.bind_('/session/:sessionId/window_handle').
134
+ on(Request.Method.GET, Dispatcher.executeAs('getCurrentWindowHandle'));
135
+ this.bind_('/session/:sessionId/window_handles').
136
+ on(Request.Method.GET, Dispatcher.executeAs('getWindowHandles'));
137
+
138
+ this.bind_('/session/:sessionId/speed').
139
+ on(Request.Method.GET, Dispatcher.executeAs('getSpeed')).
140
+ on(Request.Method.POST, Dispatcher.executeAs('setSpeed'));
141
+
142
+ this.bind_('/session/:sessionId/url').
143
+ on(Request.Method.GET, Dispatcher.executeAs('getCurrentUrl')).
144
+ on(Request.Method.POST, Dispatcher.executeAs('get'));
145
+
146
+ this.bind_('/session/:sessionId/forward').
147
+ on(Request.Method.POST, Dispatcher.executeAs('goForward'));
148
+ this.bind_('/session/:sessionId/back').
149
+ on(Request.Method.POST, Dispatcher.executeAs('goBack'));
150
+ this.bind_('/session/:sessionId/refresh').
151
+ on(Request.Method.POST, Dispatcher.executeAs('refresh'));
152
+
153
+ this.bind_('/session/:sessionId/execute').
154
+ on(Request.Method.POST, Dispatcher.executeAs('executeScript'));
155
+
156
+ this.bind_('/session/:sessionId/source').
157
+ on(Request.Method.GET, Dispatcher.executeAs('getPageSource'));
158
+ this.bind_('/session/:sessionId/title').
159
+ on(Request.Method.GET, Dispatcher.executeAs('getTitle'));
160
+
161
+ this.bind_('/session/:sessionId/element').
162
+ on(Request.Method.POST, Dispatcher.executeAs('findElement'));
163
+ this.bind_('/session/:sessionId/elements').
164
+ on(Request.Method.POST, Dispatcher.executeAs('findElements'));
165
+ this.bind_('/session/:sessionId/element/active').
166
+ on(Request.Method.POST, Dispatcher.executeAs('getActiveElement'));
167
+
168
+ this.bind_('/session/:sessionId/element/:id').
169
+ // TODO: implement
170
+ on(Request.Method.GET, Dispatcher.notImplemented);
171
+
172
+ this.bind_('/session/:sessionId/element/:id/element').
173
+ on(Request.Method.POST, Dispatcher.executeAs('findChildElement'));
174
+ this.bind_('/session/:sessionId/element/:id/elements').
175
+ on(Request.Method.POST, Dispatcher.executeAs('findChildElements'));
176
+
177
+ this.bind_('/session/:sessionId/element/:id/click').
178
+ on(Request.Method.POST, Dispatcher.executeAs('clickElement'));
179
+ this.bind_('/session/:sessionId/element/:id/text').
180
+ on(Request.Method.GET, Dispatcher.executeAs('getElementText'));
181
+ this.bind_('/session/:sessionId/element/:id/submit').
182
+ on(Request.Method.POST, Dispatcher.executeAs('submitElement'));
183
+
184
+ this.bind_('/session/:sessionId/element/:id/value').
185
+ on(Request.Method.POST, Dispatcher.executeAs('sendKeysToElement')).
186
+ on(Request.Method.GET, Dispatcher.executeAs('getElementValue'));
187
+
188
+ this.bind_('/session/:sessionId/element/:id/name').
189
+ on(Request.Method.GET, Dispatcher.executeAs('getElementTagName'));
190
+
191
+ this.bind_('/session/:sessionId/element/:id/clear').
192
+ on(Request.Method.POST, Dispatcher.executeAs('clearElement'));
193
+
194
+ this.bind_('/session/:sessionId/element/:id/selected').
195
+ on(Request.Method.GET, Dispatcher.executeAs('isElementSelected')).
196
+ on(Request.Method.POST, Dispatcher.executeAs('setElementSelected'));
197
+
198
+ this.bind_('/session/:sessionId/element/:id/enabled').
199
+ on(Request.Method.GET, Dispatcher.executeAs('isElementEnabled'));
200
+ this.bind_('/session/:sessionId/element/:id/displayed').
201
+ on(Request.Method.GET, Dispatcher.executeAs('isElementDisplayed'));
202
+
203
+ this.bind_('/session/:sessionId/element/:id/location').
204
+ on(Request.Method.GET, Dispatcher.executeAs('getElementLocation'));
205
+ this.bind_('/session/:sessionId/element/:id/location_in_view').
206
+ on(Request.Method.GET, Dispatcher.executeAs(
207
+ 'getElementLocationOnceScrolledIntoView'));
208
+
209
+ this.bind_('/session/:sessionId/element/:id/size').
210
+ on(Request.Method.GET, Dispatcher.executeAs('getElementSize'));
211
+
212
+ this.bind_('/session/:sessionId/element/:id/css/:propertyName').
213
+ on(Request.Method.GET,
214
+ Dispatcher.executeAs('getElementValueOfCssProperty'));
215
+ this.bind_('/session/:sessionId/element/:id/attribute/:name').
216
+ on(Request.Method.GET, Dispatcher.executeAs('getElementAttribute'));
217
+ this.bind_('/session/:sessionId/element/:id/equals/:other').
218
+ on(Request.Method.GET, Dispatcher.executeAs('elementEquals'));
219
+
220
+ this.bind_('/session/:sessionId/element/:id/toggle').
221
+ on(Request.Method.POST, Dispatcher.executeAs('toggleElement'));
222
+ this.bind_('/session/:sessionId/element/:id/hover').
223
+ on(Request.Method.POST, Dispatcher.executeAs('hoverOverElement'));
224
+ this.bind_('/session/:sessionId/element/:id/drag').
225
+ on(Request.Method.POST, Dispatcher.executeAs('dragElement'));
226
+
227
+ this.bind_('/session/:sessionId/cookie').
228
+ on(Request.Method.GET, Dispatcher.executeAs('getCookies')).
229
+ on(Request.Method.POST, Dispatcher.executeAs('addCookie')).
230
+ on(Request.Method.DELETE, Dispatcher.executeAs('deleteAllCookies'));
231
+
232
+ this.bind_('/session/:sessionId/cookie/:name').
233
+ on(Request.Method.DELETE, Dispatcher.executeAs('deleteCookie'));
234
+
235
+ this.bind_('/session/:sessionId/frame').
236
+ on(Request.Method.POST, Dispatcher.executeAs('switchToFrame'));
237
+ this.bind_('/session/:sessionId/window').
238
+ on(Request.Method.POST, Dispatcher.executeAs('switchToWindow')).
239
+ on(Request.Method.DELETE, Dispatcher.executeAs('close'));
240
+
241
+ this.bind_('/session/:sessionId/screenshot').
242
+ on(Request.Method.GET, Dispatcher.executeAs('screenshot'));
243
+
244
+
245
+ // --------------------------------------------------------------------------
246
+ // Firefox extensions to the wire protocol.
247
+ // --------------------------------------------------------------------------
248
+
249
+ this.bind_('/extensions/firefox/quit').
250
+ on(Request.Method.POST, Dispatcher.executeAs('quit'));
251
+ };
252
+
253
+
254
+ /**
255
+ * Binds a resource to the given path.
256
+ * @param {string} path The resource path.
257
+ * @return {Resource} The bound resource.
258
+ */
259
+ Dispatcher.prototype.bind_ = function(path) {
260
+ var resource = new Resource(path);
261
+ this.resources_.push(resource);
262
+ return resource;
263
+ };
264
+
265
+
266
+
267
+ /**
268
+ * Dispatches a request to the appropriately registered handler.
269
+ * @param {Request} request The request to dispatch.
270
+ * @param {Response} response The request response.
271
+ */
272
+ Dispatcher.prototype.dispatch = function(request, response) {
273
+ // We only support one servlet, mapped to /hub/*
274
+ // TODO: be more flexible.
275
+ var path = request.getRequestUrl().path;
276
+ if (path.indexOf('/hub') != 0) {
277
+ response.sendError(Response.NOT_FOUND);
278
+ return;
279
+ }
280
+ request.setServletPath('/hub');
281
+ path = request.getPathInfo();
282
+
283
+ var bestMatchResource;
284
+ for (var i = 0; i < this.resources_.length; i++) {
285
+ if (this.resources_[i].isResourceFor(path)) {
286
+ if (!bestMatchResource ||
287
+ bestMatchResource.getNumVariablePathSegments() <
288
+ this.resources_[i].getNumVariablePathSegments()) {
289
+ bestMatchResource = this.resources_[i];
290
+ }
291
+ }
292
+ }
293
+
294
+ if (bestMatchResource) {
295
+ try {
296
+ bestMatchResource.setRequestAttributes(request);
297
+ bestMatchResource.handle(request, response);
298
+ } catch (ex) {
299
+ Utils.dump(ex);
300
+ response.sendError(Response.INTERNAL_ERROR, JSON.stringify({
301
+ status: ErrorCode.UNHANDLED_ERROR,
302
+ value: ErrorCode.toJSON(ex)
303
+ }), 'application/json');
304
+ }
305
+ } else {
306
+ response.sendError(Response.NOT_FOUND,
307
+ 'Unrecognized command: ' + request.getMethod() + ' ' +
308
+ request.getPathInfo(),
309
+ 'text/plain');
310
+ }
311
+ };
312
+
313
+
314
+ /**
315
+ * Defines a resource in the WebDriver REST service locatable at the given path.
316
+ * Any path segments prefixed with a ":" indicate that segment is a variable
317
+ * unique to a resource. For example, in the path "/session/:sessionId",
318
+ * ":sessionId" is a variable that can be changed to specify different sessions.
319
+ * @param {!string} path The path that this resource is accessible from.
320
+ */
321
+ function Resource(path) {
322
+
323
+ /**
324
+ * The request pattern that this resource is located at.
325
+ * @type {!string}
326
+ * @const
327
+ * @private
328
+ */
329
+ this.path_ = path;
330
+
331
+ /**
332
+ * The individual path segments for this resource.
333
+ * @type {Array.<string>}
334
+ * @const
335
+ * @private
336
+ */
337
+ this.pathSegments_ = path.split('/');
338
+
339
+ /**
340
+ * A map of handler functions, by HTTP method, that can service requests to
341
+ * this resource.
342
+ * @type {!Object}
343
+ * @const
344
+ * @private
345
+ */
346
+ this.handlers_ = {};
347
+
348
+ for (var i = 0; i < this.pathSegments_.length; i++) {
349
+ if (this.pathSegments_[i].indexOf(Resource.VARIABLE_PATH_SEGMENT_PREFIX_)) {
350
+ this.numVariablePathSegments_ += 1;
351
+ }
352
+ }
353
+ };
354
+
355
+
356
+ /**
357
+ * The number of path segments for this resource that are variables.
358
+ * @type {number}
359
+ * @private
360
+ */
361
+ Resource.prototype.numVariablePathSegments_ = 0;
362
+
363
+
364
+ /** @return {string} The path mapped to this resource. */
365
+ Resource.prototype.getPath = function() {
366
+ return this.path_;
367
+ };
368
+
369
+
370
+ /** @return {number} The number of variable path segments for this resource. */
371
+ Resource.prototype.getNumVariablePathSegments = function() {
372
+ return this.numVariablePathSegments_;
373
+ };
374
+
375
+
376
+ /**
377
+ * Sets the handler function for this resource when a request is received using
378
+ * the given HTTP method. This function will override any previously set
379
+ * handlers.
380
+ * @param {!Request.Method} httpMethod The request method the function can
381
+ * handle.
382
+ * @param {function(Request, Response)} handlerFn The function that will handle
383
+ * all requests for this resource using the given HTTP method.
384
+ * @return {!Resource} A self reference for chained calls.
385
+ */
386
+ Resource.prototype.on = function(httpMethod, handlerFn) {
387
+ this.handlers_[httpMethod] = handlerFn;
388
+ return this;
389
+ };
390
+
391
+
392
+ /**
393
+ * Determines if this is the resource for the given path.
394
+ * @param {!string} path The resource path to test.
395
+ * @return {boolean} Whether this resource is mapped to the given path.
396
+ */
397
+ Resource.prototype.isResourceFor = function(path) {
398
+ var allParts = path.split('/');
399
+ if (this.pathSegments_.length != allParts.length) {
400
+ return false;
401
+ }
402
+ for (var i = 0; i < this.pathSegments_.length; i++) {
403
+ if (this.pathSegments_[i] != allParts[i] &&
404
+ !/^:/.test(this.pathSegments_[i])) {
405
+ return false;
406
+ }
407
+ }
408
+ return true;
409
+ };
410
+
411
+
412
+ /**
413
+ * Sets request attributes by the named path variables for this resource. For
414
+ * each named path segment variable for this resource, the value of the
415
+ * corresponding path segment in the request will be stored as the request
416
+ * attribute's value.
417
+ * @param {Request} request The request to update.
418
+ */
419
+ Resource.prototype.setRequestAttributes = function(request) {
420
+ var allParts = request.getPathInfo().split('/');
421
+ for (var i = 0; i < this.pathSegments_.length; i++) {
422
+ if (/^:/.test(this.pathSegments_[i])) {
423
+ var decodedValue = decodeURIComponent(allParts[i]);
424
+ request.setAttribute(
425
+ this.pathSegments_[i].replace(/^:/, ''), decodedValue);
426
+ }
427
+ }
428
+ };
429
+
430
+
431
+ /**
432
+ * Handles a request to this resource. Will return a 405 if this resource does
433
+ * not permit the HTTP method used for the request.
434
+ * @param {Request} request The request to handle.
435
+ * @param {Response} response For sending the response.
436
+ * @throws If this resource cannot handle the request.
437
+ */
438
+ Resource.prototype.handle = function(request, response) {
439
+ if (!this.isResourceFor(request.getPathInfo())) {
440
+ throw Error('Request does not map to this resource:' +
441
+ '\n requestPath: ' + request.getPathInfo() +
442
+ '\n resourcePath: ' + this.path_);;
443
+ }
444
+
445
+ var requestMethod = request.getMethod();
446
+
447
+ if (requestMethod == Request.Method.OPTIONS) {
448
+ response.setHeader('Allow', this.getAllowedMethods_());
449
+ response.setStatus(Response.OK);
450
+ response.setBody('');
451
+ response.commit();
452
+ return;
453
+ }
454
+
455
+ if (requestMethod == Request.Method.HEAD) {
456
+ requestMethod = Request.Method.GET;
457
+ }
458
+
459
+ var handlerFn = this.handlers_[requestMethod];
460
+ if (handlerFn) {
461
+ handlerFn(request, response);
462
+ } else {
463
+ response.setHeader('Allow', this.getAllowedMethods_());
464
+ response.setContentType('text/plain');
465
+ response.sendError(Response.METHOD_NOT_ALLOWED,
466
+ 'Method "' + request.getMethod() + '" not allowed for command ' +
467
+ '"' + this.path_ + '"');
468
+ }
469
+ };
470
+
471
+
472
+ /**
473
+ * @return {string} A comma-delimitted list of HTTP methods allowed by this
474
+ * resource.
475
+ * @private
476
+ */
477
+ Resource.prototype.getAllowedMethods_ = function() {
478
+ var allowed = [];
479
+ for (var method in this.handlers_) {
480
+ allowed.push(method);
481
+ }
482
+
483
+ // We always respond to OPTIONS
484
+ allowed.push(Request.Method.OPTIONS);
485
+
486
+ // If we respond to GET, then we respond to HEAD.
487
+ if (Request.Method.GET in this.handlers_) {
488
+ allowed.push(Request.Method.HEAD);
489
+ }
490
+
491
+ return allowed.join(',');
492
+ };