Project

General

Profile

1
/*
2
 * Copyright 2004 ThoughtWorks, Inc
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 *
16
 */
17

    
18
// This script contains a badly-organised collection of miscellaneous
19
// functions that really better homes.
20

    
21
function classCreate() {
22
    return function() {
23
      this.initialize.apply(this, arguments);
24
    }
25
}
26

    
27
function objectExtend(destination, source) {
28
  for (var property in source) {
29
    destination[property] = source[property];
30
  }
31
  return destination;
32
}
33

    
34
function $() {
35
  var results = [], element;
36
  for (var i = 0; i < arguments.length; i++) {
37
    element = arguments[i];
38
    if (typeof element == 'string')
39
      element = document.getElementById(element);
40
    results[results.length] = element;
41
  }
42
  return results.length < 2 ? results[0] : results;
43
}
44

    
45
function $A(iterable) {
46
  if (!iterable) return [];
47
  if (iterable.toArray) {
48
    return iterable.toArray();
49
  } else {
50
    var results = [];
51
    for (var i = 0; i < iterable.length; i++)
52
      results.push(iterable[i]);
53
    return results;
54
  }
55
}
56

    
57
function fnBind() {
58
  var args = $A(arguments), __method = args.shift(), object = args.shift();
59
  var retval = function() {
60
    return __method.apply(object, args.concat($A(arguments)));
61
  }
62
  retval.__method = __method;
63
  return retval;
64
}
65

    
66
function fnBindAsEventListener(fn, object) {
67
  var __method = fn;
68
  return function(event) {
69
    return __method.call(object, event || window.event);
70
  }
71
}
72

    
73
function removeClassName(element, name) {
74
    var re = new RegExp("\\b" + name + "\\b", "g");
75
    element.className = element.className.replace(re, "");
76
}
77

    
78
function addClassName(element, name) {
79
    element.className = element.className + ' ' + name;
80
}
81

    
82
function elementSetStyle(element, style) {
83
    for (var name in style) {
84
      var value = style[name];
85
      if (value == null) value = "";
86
      element.style[name] = value;
87
    }
88
}
89

    
90
function elementGetStyle(element, style) {
91
    var value = element.style[style];
92
    if (!value) {
93
      if (document.defaultView && document.defaultView.getComputedStyle) {
94
        var css = document.defaultView.getComputedStyle(element, null);
95
        value = css ? css.getPropertyValue(style) : null;
96
      } else if (element.currentStyle) {
97
        value = element.currentStyle[style];
98
      }
99
    }
100

    
101
    /** DGF necessary? 
102
    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
103
      if (Element.getStyle(element, 'position') == 'static') value = 'auto'; */
104

    
105
    return value == 'auto' ? null : value;
106
  }
107

    
108
String.prototype.trim = function() {
109
    var result = this.replace(/^\s+/g, "");
110
    // strip leading
111
    return result.replace(/\s+$/g, "");
112
    // strip trailing
113
};
114
String.prototype.lcfirst = function() {
115
    return this.charAt(0).toLowerCase() + this.substr(1);
116
};
117
String.prototype.ucfirst = function() {
118
    return this.charAt(0).toUpperCase() + this.substr(1);
119
};
120
String.prototype.startsWith = function(str) {
121
    return this.indexOf(str) == 0;
122
};
123

    
124
// Returns the text in this element
125
function getText(element) {
126
    var text = "";
127

    
128
    var isRecentFirefox = (browserVersion.isFirefox && browserVersion.firefoxVersion >= "1.5");
129
    if (isRecentFirefox || browserVersion.isKonqueror || browserVersion.isSafari || browserVersion.isOpera) {
130
        text = getTextContent(element);
131
    } else if (element.textContent) {
132
        text = element.textContent;
133
    } else if (element.innerText) {
134
        text = element.innerText;
135
    }
136

    
137
    text = normalizeNewlines(text);
138
    text = normalizeSpaces(text);
139

    
140
    return text.trim();
141
}
142

    
143
function getTextContent(element, preformatted) {
144
    if (element.nodeType == 3 /*Node.TEXT_NODE*/) {
145
        var text = element.data;
146
        if (!preformatted) {
147
            text = text.replace(/\n|\r|\t/g, " ");
148
        }
149
        return text;
150
    }
151
    if (element.nodeType == 1 /*Node.ELEMENT_NODE*/) {
152
        var childrenPreformatted = preformatted || (element.tagName == "PRE");
153
        var text = "";
154
        for (var i = 0; i < element.childNodes.length; i++) {
155
            var child = element.childNodes.item(i);
156
            text += getTextContent(child, childrenPreformatted);
157
        }
158
        // Handle block elements that introduce newlines
159
        // -- From HTML spec:
160
        //<!ENTITY % block
161
        //     "P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT |
162
        //      BLOCKQUOTE | F:wORM | HR | TABLE | FIELDSET | ADDRESS">
163
        //
164
        // TODO: should potentially introduce multiple newlines to separate blocks
165
        if (element.tagName == "P" || element.tagName == "BR" || element.tagName == "HR" || element.tagName == "DIV") {
166
            text += "\n";
167
        }
168
        return text;
169
    }
170
    return '';
171
}
172

    
173
/**
174
 * Convert all newlines to \m
175
 */
176
function normalizeNewlines(text)
177
{
178
    return text.replace(/\r\n|\r/g, "\n");
179
}
180

    
181
/**
182
 * Replace multiple sequential spaces with a single space, and then convert &nbsp; to space.
183
 */
184
function normalizeSpaces(text)
185
{
186
    // IE has already done this conversion, so doing it again will remove multiple nbsp
187
    if (browserVersion.isIE)
188
    {
189
        return text;
190
    }
191

    
192
    // Replace multiple spaces with a single space
193
    // TODO - this shouldn't occur inside PRE elements
194
    text = text.replace(/\ +/g, " ");
195

    
196
    // Replace &nbsp; with a space
197
    var nbspPattern = new RegExp(String.fromCharCode(160), "g");
198
    if (browserVersion.isSafari) {
199
	return replaceAll(text, String.fromCharCode(160), " ");
200
    } else {
201
	return text.replace(nbspPattern, " ");
202
    }
203
}
204

    
205
function replaceAll(text, oldText, newText) {
206
    while (text.indexOf(oldText) != -1) {
207
	text = text.replace(oldText, newText);
208
    }
209
    return text;
210
}
211

    
212

    
213
function xmlDecode(text) {
214
    text = text.replace(/&quot;/g, '"');
215
    text = text.replace(/&apos;/g, "'");
216
    text = text.replace(/&lt;/g, "<");
217
    text = text.replace(/&gt;/g, ">");
218
    text = text.replace(/&amp;/g, "&");
219
    return text;
220
}
221

    
222
// Sets the text in this element
223
function setText(element, text) {
224
    if (element.textContent != null) {
225
        element.textContent = text;
226
    } else if (element.innerText != null) {
227
        element.innerText = text;
228
    }
229
}
230

    
231
// Get the value of an <input> element
232
function getInputValue(inputElement) {
233
    if (inputElement.type) {
234
        if (inputElement.type.toUpperCase() == 'CHECKBOX' ||
235
            inputElement.type.toUpperCase() == 'RADIO')
236
        {
237
            return (inputElement.checked ? 'on' : 'off');
238
        }
239
    }
240
    if (inputElement.value == null) {
241
        throw new SeleniumError("This element has no value; is it really a form field?");
242
    }
243
    return inputElement.value;
244
}
245

    
246
/* Fire an event in a browser-compatible manner */
247
function triggerEvent(element, eventType, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
248
    canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
249
    if (element.fireEvent) {
250
        var evt = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);        
251
        element.fireEvent('on' + eventType, evt);
252
    }
253
    else {
254
        var evt = document.createEvent('HTMLEvents');
255
        
256
        try {
257
            evt.shiftKey = shiftKeyDown;
258
            evt.metaKey = metaKeyDown;
259
            evt.altKey = altKeyDown;
260
            evt.ctrlKey = controlKeyDown;
261
        } catch (e) {
262
            // On Firefox 1.0, you can only set these during initMouseEvent or initKeyEvent
263
            // we'll have to ignore them here
264
            LOG.exception(e);
265
        }
266
        
267
        evt.initEvent(eventType, canBubble, true);
268
        element.dispatchEvent(evt);
269
    }
270
}
271

    
272
function getKeyCodeFromKeySequence(keySequence) {
273
    var match = /^\\(\d{1,3})$/.exec(keySequence);
274
    if (match != null) {
275
        return match[1];
276
    }
277
    match = /^.$/.exec(keySequence);
278
    if (match != null) {
279
        return match[0].charCodeAt(0);
280
    }
281
    // this is for backward compatibility with existing tests
282
    // 1 digit ascii codes will break however because they are used for the digit chars
283
    match = /^\d{2,3}$/.exec(keySequence);
284
    if (match != null) {
285
        return match[0];
286
    }
287
    throw new SeleniumError("invalid keySequence");
288
}
289

    
290
function createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
291
     var evt = element.ownerDocument.createEventObject();
292
     evt.shiftKey = shiftKeyDown;
293
     evt.metaKey = metaKeyDown;
294
     evt.altKey = altKeyDown;
295
     evt.ctrlKey = controlKeyDown;
296
     return evt;
297
}
298

    
299
function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
300
    var keycode = getKeyCodeFromKeySequence(keySequence);
301
    canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
302
    if (element.fireEvent) {
303
        var keyEvent = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
304
        keyEvent.keyCode = keycode;
305
        element.fireEvent('on' + eventType, keyEvent);
306
    }
307
    else {
308
        var evt;
309
        if (window.KeyEvent) {
310
            evt = document.createEvent('KeyEvents');
311
            evt.initKeyEvent(eventType, true, true, window, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown, keycode, keycode);
312
        } else {
313
            evt = document.createEvent('UIEvents');
314
            
315
            evt.shiftKey = shiftKeyDown;
316
            evt.metaKey = metaKeyDown;
317
            evt.altKey = altKeyDown;
318
            evt.ctrlKey = controlKeyDown;
319

    
320
            evt.initUIEvent(eventType, true, true, window, 1);
321
            evt.keyCode = keycode;
322
            evt.which = keycode;
323
        }
324

    
325
        element.dispatchEvent(evt);
326
    }
327
}
328

    
329
function removeLoadListener(element, command) {
330
    LOG.info('Removing loadListenter for ' + element + ', ' + command);
331
    if (window.removeEventListener)
332
        element.removeEventListener("load", command, true);
333
    else if (window.detachEvent)
334
        element.detachEvent("onload", command);
335
}
336

    
337
function addLoadListener(element, command) {
338
    LOG.info('Adding loadListenter for ' + element + ', ' + command);
339
    var augmentedCommand = function() {
340
        command.call(this, element);
341
    }
342
    if (window.addEventListener && !browserVersion.isOpera)
343
        element.addEventListener("load", augmentedCommand, true);
344
    else if (window.attachEvent)
345
        element.attachEvent("onload", augmentedCommand);
346
}
347

    
348
/**
349
 * Override the broken getFunctionName() method from JsUnit
350
 * This file must be loaded _after_ the jsunitCore.js
351
 */
352
function getFunctionName(aFunction) {
353
    var regexpResult = aFunction.toString().match(/function (\w*)/);
354
    if (regexpResult && regexpResult[1]) {
355
        return regexpResult[1];
356
    }
357
    return 'anonymous';
358
}
359

    
360
function getDocumentBase(doc) {
361
    var bases = document.getElementsByTagName("base");
362
    if (bases && bases.length && bases[0].href) {
363
        return bases[0].href;
364
    }
365
    return "";
366
}
367

    
368
function getTagName(element) {
369
    var tagName;
370
    if (element && element.tagName && element.tagName.toLowerCase) {
371
        tagName = element.tagName.toLowerCase();
372
    }
373
    return tagName;
374
}
375

    
376
function absolutify(url, baseUrl) {
377
    /** returns a relative url in its absolute form, given by baseUrl.
378
    * 
379
    * This function is a little odd, because it can take baseUrls that
380
    * aren't necessarily directories.  It uses the same rules as the HTML 
381
    * &lt;base&gt; tag; if the baseUrl doesn't end with "/", we'll assume
382
    * that it points to a file, and strip the filename off to find its
383
    * base directory.
384
    *
385
    * So absolutify("foo", "http://x/bar") will return "http://x/foo" (stripping off bar),
386
    * whereas absolutify("foo", "http://x/bar/") will return "http://x/bar/foo" (preserving bar).
387
    * Naturally absolutify("foo", "http://x") will return "http://x/foo", appropriately.
388
    * 
389
    * @param url the url to make absolute; if this url is already absolute, we'll just return that, unchanged
390
    * @param baseUrl the baseUrl from which we'll absolutify, following the rules above.
391
    * @return 'url' if it was already absolute, or the absolutized version of url if it was not absolute.
392
    */
393
    
394
    // DGF isn't there some library we could use for this?
395
        
396
    if (/^\w+:/.test(url)) {
397
        // it's already absolute
398
        return url;
399
    }
400
    
401
    var loc;
402
    try {
403
        loc = parseUrl(baseUrl);
404
    } catch (e) {
405
        // is it an absolute windows file path? let's play the hero in that case
406
        if (/^\w:\\/.test(baseUrl)) {
407
            baseUrl = "file:///" + baseUrl.replace(/\\/g, "/");
408
            loc = parseUrl(baseUrl);
409
        } else {
410
            throw new SeleniumError("baseUrl wasn't absolute: " + baseUrl);
411
        }
412
    }
413
    loc.search = null;
414
    loc.hash = null;
415
    
416
    // if url begins with /, then that's the whole pathname
417
    if (/^\//.test(url)) {
418
        loc.pathname = url;
419
        var result = reassembleLocation(loc);
420
        return result;
421
    }
422
    
423
    // if pathname is null, then we'll just append "/" + the url
424
    if (!loc.pathname) {
425
        loc.pathname = "/" + url;
426
        var result = reassembleLocation(loc);
427
        return result;
428
    }
429
    
430
    // if pathname ends with /, just append url
431
    if (/\/$/.test(loc.pathname)) {
432
        loc.pathname += url;
433
        var result = reassembleLocation(loc);
434
        return result;
435
    }
436
    
437
    // if we're here, then the baseUrl has a pathname, but it doesn't end with /
438
    // in that case, we replace everything after the final / with the relative url
439
    loc.pathname = loc.pathname.replace(/[^\/\\]+$/, url);
440
    var result = reassembleLocation(loc);
441
    return result;
442
    
443
}
444

    
445
var URL_REGEX = /^((\w+):\/\/)(([^:]+):?([^@]+)?@)?([^\/\?:]*):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(.+)?/;
446

    
447
function parseUrl(url) {
448
    var fields = ['url', null, 'protocol', null, 'username', 'password', 'host', 'port', 'pathname', 'search', 'hash'];
449
    var result = URL_REGEX.exec(url);
450
    if (!result) {
451
        throw new SeleniumError("Invalid URL: " + url);
452
    }
453
    var loc = new Object();
454
    for (var i = 0; i < fields.length; i++) {
455
        var field = fields[i];
456
        if (field == null) {
457
            continue;
458
        }
459
        loc[field] = result[i];
460
    }
461
    return loc;
462
}
463

    
464
function reassembleLocation(loc) {
465
    if (!loc.protocol) {
466
        throw new Error("Not a valid location object: " + o2s(loc));
467
    }
468
    var protocol = loc.protocol;
469
    protocol = protocol.replace(/:$/, "");
470
    var url = protocol + "://";
471
    if (loc.username) {
472
        url += loc.username;
473
        if (loc.password) {
474
            url += ":" + loc.password;
475
        }
476
        url += "@";
477
    }
478
    if (loc.host) {
479
        url += loc.host;
480
    }
481
    
482
    if (loc.port) {
483
        url += ":" + loc.port;
484
    }
485
    
486
    if (loc.pathname) {
487
        url += loc.pathname;
488
    }
489
    
490
    if (loc.search) {
491
        url += "?" + loc.search;
492
    }
493
    if (loc.hash) {
494
        var hash = loc.hash;
495
        hash = loc.hash.replace(/^#/, "");
496
        url += "#" + hash;
497
    }
498
    return url;
499
}
500

    
501
function canonicalize(url) {
502
    var tempLink = window.document.createElement("link");
503
    tempLink.href = url; // this will canonicalize the href
504
    return tempLink.href;
505
}
506

    
507
function extractExceptionMessage(ex) {
508
    if (ex == null) return "null exception";
509
    if (ex.message != null) return ex.message;
510
    if (ex.toString && ex.toString() != null) return ex.toString();
511
}
512
    
513

    
514
function describe(object, delimiter) {
515
    var props = new Array();
516
    for (var prop in object) {
517
        try {
518
            props.push(prop + " -> " + object[prop]);
519
        } catch (e) {
520
            props.push(prop + " -> [htmlutils: ack! couldn't read this property! (Permission Denied?)]");
521
        }
522
    }
523
    return props.join(delimiter || '\n');
524
}
525

    
526
var PatternMatcher = function(pattern) {
527
    this.selectStrategy(pattern);
528
};
529
PatternMatcher.prototype = {
530

    
531
    selectStrategy: function(pattern) {
532
        this.pattern = pattern;
533
        var strategyName = 'glob';
534
        // by default
535
        if (/^([a-z-]+):(.*)/.test(pattern)) {
536
            var possibleNewStrategyName = RegExp.$1;
537
            var possibleNewPattern = RegExp.$2;
538
            if (PatternMatcher.strategies[possibleNewStrategyName]) {
539
                strategyName = possibleNewStrategyName;
540
                pattern = possibleNewPattern;
541
            }
542
        }
543
        var matchStrategy = PatternMatcher.strategies[strategyName];
544
        if (!matchStrategy) {
545
            throw new SeleniumError("cannot find PatternMatcher.strategies." + strategyName);
546
        }
547
        this.strategy = matchStrategy;
548
        this.matcher = new matchStrategy(pattern);
549
    },
550

    
551
    matches: function(actual) {
552
        return this.matcher.matches(actual + '');
553
        // Note: appending an empty string avoids a Konqueror bug
554
    }
555

    
556
};
557

    
558
/**
559
 * A "static" convenience method for easy matching
560
 */
561
PatternMatcher.matches = function(pattern, actual) {
562
    return new PatternMatcher(pattern).matches(actual);
563
};
564

    
565
PatternMatcher.strategies = {
566

    
567
/**
568
 * Exact matching, e.g. "exact:***"
569
 */
570
    exact: function(expected) {
571
        this.expected = expected;
572
        this.matches = function(actual) {
573
            return actual == this.expected;
574
        };
575
    },
576

    
577
/**
578
 * Match by regular expression, e.g. "regexp:^[0-9]+$"
579
 */
580
    regexp: function(regexpString) {
581
        this.regexp = new RegExp(regexpString);
582
        this.matches = function(actual) {
583
            return this.regexp.test(actual);
584
        };
585
    },
586

    
587
    regex: function(regexpString) {
588
        this.regexp = new RegExp(regexpString);
589
        this.matches = function(actual) {
590
            return this.regexp.test(actual);
591
        };
592
    },
593

    
594
/**
595
 * "globContains" (aka "wildmat") patterns, e.g. "glob:one,two,*",
596
 * but don't require a perfect match; instead succeed if actual
597
 * contains something that matches globString.
598
 * Making this distinction is motivated by a bug in IE6 which
599
 * leads to the browser hanging if we implement *TextPresent tests
600
 * by just matching against a regular expression beginning and
601
 * ending with ".*".  The globcontains strategy allows us to satisfy
602
 * the functional needs of the *TextPresent ops more efficiently
603
 * and so avoid running into this IE6 freeze.
604
 */
605
    globContains: function(globString) {
606
        this.regexp = new RegExp(PatternMatcher.regexpFromGlobContains(globString));
607
        this.matches = function(actual) {
608
            return this.regexp.test(actual);
609
        };
610
    },
611

    
612

    
613
/**
614
 * "glob" (aka "wildmat") patterns, e.g. "glob:one,two,*"
615
 */
616
    glob: function(globString) {
617
        this.regexp = new RegExp(PatternMatcher.regexpFromGlob(globString));
618
        this.matches = function(actual) {
619
            return this.regexp.test(actual);
620
        };
621
    }
622

    
623
};
624

    
625
PatternMatcher.convertGlobMetaCharsToRegexpMetaChars = function(glob) {
626
    var re = glob;
627
    re = re.replace(/([.^$+(){}\[\]\\|])/g, "\\$1");
628
    re = re.replace(/\?/g, "(.|[\r\n])");
629
    re = re.replace(/\*/g, "(.|[\r\n])*");
630
    return re;
631
};
632

    
633
PatternMatcher.regexpFromGlobContains = function(globContains) {
634
    return PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(globContains);
635
};
636

    
637
PatternMatcher.regexpFromGlob = function(glob) {
638
    return "^" + PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(glob) + "$";
639
};
640

    
641
var Assert = {
642

    
643
    fail: function(message) {
644
        throw new AssertionFailedError(message);
645
    },
646

    
647
/*
648
* Assert.equals(comment?, expected, actual)
649
*/
650
    equals: function() {
651
        var args = new AssertionArguments(arguments);
652
        if (args.expected === args.actual) {
653
            return;
654
        }
655
        Assert.fail(args.comment +
656
                    "Expected '" + args.expected +
657
                    "' but was '" + args.actual + "'");
658
    },
659

    
660
/*
661
* Assert.matches(comment?, pattern, actual)
662
*/
663
    matches: function() {
664
        var args = new AssertionArguments(arguments);
665
        if (PatternMatcher.matches(args.expected, args.actual)) {
666
            return;
667
        }
668
        Assert.fail(args.comment +
669
                    "Actual value '" + args.actual +
670
                    "' did not match '" + args.expected + "'");
671
    },
672

    
673
/*
674
* Assert.notMtches(comment?, pattern, actual)
675
*/
676
    notMatches: function() {
677
        var args = new AssertionArguments(arguments);
678
        if (!PatternMatcher.matches(args.expected, args.actual)) {
679
            return;
680
        }
681
        Assert.fail(args.comment +
682
                    "Actual value '" + args.actual +
683
                    "' did match '" + args.expected + "'");
684
    }
685

    
686
};
687

    
688
// Preprocess the arguments to allow for an optional comment.
689
function AssertionArguments(args) {
690
    if (args.length == 2) {
691
        this.comment = "";
692
        this.expected = args[0];
693
        this.actual = args[1];
694
    } else {
695
        this.comment = args[0] + "; ";
696
        this.expected = args[1];
697
        this.actual = args[2];
698
    }
699
}
700

    
701
function AssertionFailedError(message) {
702
    this.isAssertionFailedError = true;
703
    this.isSeleniumError = true;
704
    this.message = message;
705
    this.failureMessage = message;
706
}
707

    
708
function SeleniumError(message) {
709
    var error = new Error(message);
710
    error.isSeleniumError = true;
711
    return error;
712
}
713

    
714
function highlight(element) {
715
    var highLightColor = "yellow";
716
    if (element.originalColor == undefined) { // avoid picking up highlight
717
        element.originalColor = elementGetStyle(element, "background-color");
718
    }
719
    elementSetStyle(element, {"backgroundColor" : highLightColor});
720
    window.setTimeout(function() {
721
        try {
722
            //if element is orphan, probably page of it has already gone, so ignore
723
            if (!element.parentNode) {
724
                return;
725
            }
726
            elementSetStyle(element, {"backgroundColor" : element.originalColor});
727
        } catch (e) {} // DGF unhighlighting is very dangerous and low priority
728
    }, 200);
729
}
730

    
731

    
732

    
733
// for use from vs.2003 debugger
734
function o2s(obj) {
735
    var s = "";
736
    for (key in obj) {
737
        var line = key + "->" + obj[key];
738
        line.replace("\n", " ");
739
        s += line + "\n";
740
    }
741
    return s;
742
}
743

    
744
var seenReadyStateWarning = false;
745

    
746
function openSeparateApplicationWindow(url, suppressMozillaWarning) {
747
    // resize the Selenium window itself
748
    window.resizeTo(1200, 500);
749
    window.moveTo(window.screenX, 0);
750

    
751
    var appWindow = window.open(url + '?start=true', 'main');
752
    try {
753
        var windowHeight = 500;
754
        if (window.outerHeight) {
755
            windowHeight = window.outerHeight;
756
        } else if (document.documentElement && document.documentElement.offsetHeight) {
757
            windowHeight = document.documentElement.offsetHeight;
758
        }
759

    
760
        if (window.screenLeft && !window.screenX) window.screenX = window.screenLeft;
761
        if (window.screenTop && !window.screenY) window.screenY = window.screenTop;
762

    
763
        appWindow.resizeTo(1200, screen.availHeight - windowHeight - 60);
764
        appWindow.moveTo(window.screenX, window.screenY + windowHeight + 25);
765
    } catch (e) {
766
        LOG.error("Couldn't resize app window");
767
        LOG.exception(e);
768
    }
769

    
770

    
771
    if (!suppressMozillaWarning && window.document.readyState == null && !seenReadyStateWarning) {
772
        alert("Beware!  Mozilla bug 300992 means that we can't always reliably detect when a new page has loaded.  Install the Selenium IDE extension or the readyState extension available from selenium.openqa.org to make page load detection more reliable.");
773
        seenReadyStateWarning = true;
774
    }
775

    
776
    return appWindow;
777
}
778

    
779
var URLConfiguration = classCreate();
780
objectExtend(URLConfiguration.prototype, {
781
    initialize: function() {
782
    },
783
    _isQueryParameterTrue: function (name) {
784
        var parameterValue = this._getQueryParameter(name);
785
        if (parameterValue == null) return false;
786
        if (parameterValue.toLowerCase() == "true") return true;
787
        if (parameterValue.toLowerCase() == "on") return true;
788
        return false;
789
    },
790

    
791
    _getQueryParameter: function(searchKey) {
792
        var str = this.queryString
793
        if (str == null) return null;
794
        var clauses = str.split('&');
795
        for (var i = 0; i < clauses.length; i++) {
796
            var keyValuePair = clauses[i].split('=', 2);
797
            var key = unescape(keyValuePair[0]);
798
            if (key == searchKey) {
799
                return unescape(keyValuePair[1]);
800
            }
801
        }
802
        return null;
803
    },
804

    
805
    _extractArgs: function() {
806
        var str = SeleniumHTARunner.commandLine;
807
        if (str == null || str == "") return new Array();
808
        var matches = str.match(/(?:\"([^\"]+)\"|(?!\"([^\"]+)\")(\S+))/g);
809
        // We either want non quote stuff ([^"]+) surrounded by quotes
810
        // or we want to look-ahead, see that the next character isn't
811
        // a quoted argument, and then grab all the non-space stuff
812
        // this will return for the line: "foo" bar
813
        // the results "\"foo\"" and "bar"
814

    
815
        // So, let's unquote the quoted arguments:
816
        var args = new Array;
817
        for (var i = 0; i < matches.length; i++) {
818
            args[i] = matches[i];
819
            args[i] = args[i].replace(/^"(.*)"$/, "$1");
820
        }
821
        return args;
822
    },
823

    
824
    isMultiWindowMode:function() {
825
        return this._isQueryParameterTrue('multiWindow');
826
    },
827
    
828
    getBaseUrl:function() {
829
        return this._getQueryParameter('baseUrl');
830
            
831
    }
832
});
833

    
834

    
835
function safeScrollIntoView(element) {
836
    if (element.scrollIntoView) {
837
        element.scrollIntoView(false);
838
        return;
839
    }
840
    // TODO: work out how to scroll browsers that don't support
841
    // scrollIntoView (like Konqueror)
842
}
(2-2/20)