Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/debug/devcss/devcss.js
2868 views
1
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview Runtime development CSS Compiler emulation, via javascript.
17
* This class provides an approximation to CSSCompiler's functionality by
18
* hacking the live CSSOM.
19
* This code is designed to be inserted in the DOM immediately after the last
20
* style block in HEAD when in development mode, i.e. you are not using a
21
* running instance of a CSS Compiler to pass your CSS through.
22
*/
23
24
25
goog.provide('goog.debug.DevCss');
26
goog.provide('goog.debug.DevCss.UserAgent');
27
28
goog.require('goog.asserts');
29
goog.require('goog.cssom');
30
goog.require('goog.dom.classlist');
31
goog.require('goog.events');
32
goog.require('goog.events.EventType');
33
goog.require('goog.string');
34
goog.require('goog.userAgent');
35
36
37
38
/**
39
* A class for solving development CSS issues/emulating the CSS Compiler.
40
* @param {goog.debug.DevCss.UserAgent=} opt_userAgent The user agent, if not
41
* passed in, will be determined using goog.userAgent.
42
* @param {number|string=} opt_userAgentVersion The user agent's version.
43
* If not passed in, will be determined using goog.userAgent.
44
* @throws {Error} When userAgent detection fails.
45
* @constructor
46
* @final
47
*/
48
goog.debug.DevCss = function(opt_userAgent, opt_userAgentVersion) {
49
if (!opt_userAgent) {
50
// Walks through the known goog.userAgents.
51
if (goog.userAgent.IE) {
52
opt_userAgent = goog.debug.DevCss.UserAgent.IE;
53
} else if (goog.userAgent.GECKO) {
54
opt_userAgent = goog.debug.DevCss.UserAgent.GECKO;
55
} else if (goog.userAgent.WEBKIT) {
56
opt_userAgent = goog.debug.DevCss.UserAgent.WEBKIT;
57
} else if (goog.userAgent.MOBILE) {
58
opt_userAgent = goog.debug.DevCss.UserAgent.MOBILE;
59
} else if (goog.userAgent.OPERA) {
60
opt_userAgent = goog.debug.DevCss.UserAgent.OPERA;
61
} else if (goog.userAgent.EDGE) {
62
opt_userAgent = goog.debug.DevCss.UserAgent.EDGE;
63
}
64
}
65
switch (opt_userAgent) {
66
case goog.debug.DevCss.UserAgent.OPERA:
67
case goog.debug.DevCss.UserAgent.IE:
68
case goog.debug.DevCss.UserAgent.GECKO:
69
case goog.debug.DevCss.UserAgent.FIREFOX:
70
case goog.debug.DevCss.UserAgent.WEBKIT:
71
case goog.debug.DevCss.UserAgent.SAFARI:
72
case goog.debug.DevCss.UserAgent.MOBILE:
73
case goog.debug.DevCss.UserAgent.EDGE:
74
break;
75
default:
76
throw Error('Could not determine the user agent from known UserAgents');
77
}
78
79
/**
80
* One of goog.debug.DevCss.UserAgent.
81
* @type {string}
82
* @private
83
*/
84
this.userAgent_ = opt_userAgent;
85
86
/**
87
* @const @private
88
*/
89
this.userAgentTokens_ = {};
90
91
/**
92
* @type {number|string}
93
* @private
94
*/
95
this.userAgentVersion_ = opt_userAgentVersion || goog.userAgent.VERSION;
96
this.generateUserAgentTokens_();
97
98
/**
99
* @type {boolean}
100
* @private
101
*/
102
this.isIe6OrLess_ = this.userAgent_ == goog.debug.DevCss.UserAgent.IE &&
103
goog.string.compareVersions('7', this.userAgentVersion_) > 0;
104
105
if (this.isIe6OrLess_) {
106
/**
107
* @type {Array<{classNames,combinedClassName,els}>}
108
* @private
109
*/
110
this.ie6CombinedMatches_ = [];
111
}
112
};
113
114
115
/**
116
* Rewrites the CSSOM as needed to activate any useragent-specific selectors.
117
* @param {boolean=} opt_enableIe6ReadyHandler If true(the default), and the
118
* userAgent is ie6, we set a document "ready" event handler to walk the DOM
119
* and make combined selector className changes. Having this parameter also
120
* aids unit testing.
121
*/
122
goog.debug.DevCss.prototype.activateBrowserSpecificCssRules = function(
123
opt_enableIe6ReadyHandler) {
124
var enableIe6EventHandler =
125
goog.isDef(opt_enableIe6ReadyHandler) ? opt_enableIe6ReadyHandler : true;
126
var cssRules = goog.cssom.getAllCssStyleRules();
127
128
for (var i = 0, cssRule; cssRule = cssRules[i]; i++) {
129
this.replaceBrowserSpecificClassNames_(cssRule);
130
}
131
132
// Since we may have manipulated the rules above, we'll have to do a
133
// complete sweep again if we're in IE6. Luckily performance doesn't
134
// matter for this tool.
135
if (this.isIe6OrLess_) {
136
cssRules = goog.cssom.getAllCssStyleRules();
137
for (var i = 0, cssRule; cssRule = cssRules[i]; i++) {
138
this.replaceIe6CombinedSelectors_(cssRule);
139
}
140
}
141
142
// Add an event listener for document ready to rewrite any necessary
143
// combined classnames in IE6.
144
if (this.isIe6OrLess_ && enableIe6EventHandler) {
145
goog.events.listen(
146
document, goog.events.EventType.LOAD,
147
goog.bind(this.addIe6CombinedClassNames_, this));
148
}
149
};
150
151
152
/**
153
* A list of possible user agent strings.
154
* @enum {string}
155
*/
156
goog.debug.DevCss.UserAgent = {
157
OPERA: 'OPERA',
158
IE: 'IE',
159
GECKO: 'GECKO',
160
FIREFOX: 'GECKO',
161
WEBKIT: 'WEBKIT',
162
SAFARI: 'WEBKIT',
163
MOBILE: 'MOBILE',
164
EDGE: 'EDGE'
165
};
166
167
168
/**
169
* A list of strings that may be used for matching in CSS files/development.
170
* @enum {string}
171
* @private
172
*/
173
goog.debug.DevCss.CssToken_ = {
174
USERAGENT: 'USERAGENT',
175
SEPARATOR: '-',
176
LESS_THAN: 'LT',
177
GREATER_THAN: 'GT',
178
LESS_THAN_OR_EQUAL: 'LTE',
179
GREATER_THAN_OR_EQUAL: 'GTE',
180
IE6_SELECTOR_TEXT: 'goog-ie6-selector',
181
IE6_COMBINED_GLUE: '_'
182
};
183
184
185
/**
186
* Generates user agent token match strings with comparison and version bits.
187
* For example:
188
* userAgentTokens_.ANY will be like 'GECKO'
189
* userAgentTokens_.LESS_THAN will be like 'GECKO-LT3' etc...
190
* @private
191
*/
192
goog.debug.DevCss.prototype.generateUserAgentTokens_ = function() {
193
this.userAgentTokens_.ANY = goog.debug.DevCss.CssToken_.USERAGENT +
194
goog.debug.DevCss.CssToken_.SEPARATOR + this.userAgent_;
195
this.userAgentTokens_.EQUALS =
196
this.userAgentTokens_.ANY + goog.debug.DevCss.CssToken_.SEPARATOR;
197
this.userAgentTokens_.LESS_THAN = this.userAgentTokens_.ANY +
198
goog.debug.DevCss.CssToken_.SEPARATOR +
199
goog.debug.DevCss.CssToken_.LESS_THAN;
200
this.userAgentTokens_.LESS_THAN_OR_EQUAL = this.userAgentTokens_.ANY +
201
goog.debug.DevCss.CssToken_.SEPARATOR +
202
goog.debug.DevCss.CssToken_.LESS_THAN_OR_EQUAL;
203
this.userAgentTokens_.GREATER_THAN = this.userAgentTokens_.ANY +
204
goog.debug.DevCss.CssToken_.SEPARATOR +
205
goog.debug.DevCss.CssToken_.GREATER_THAN;
206
this.userAgentTokens_.GREATER_THAN_OR_EQUAL = this.userAgentTokens_.ANY +
207
goog.debug.DevCss.CssToken_.SEPARATOR +
208
goog.debug.DevCss.CssToken_.GREATER_THAN_OR_EQUAL;
209
};
210
211
212
/**
213
* Gets the version number bit from a selector matching userAgentToken.
214
* @param {string} selectorText The selector text of a CSS rule.
215
* @param {string} userAgentToken Includes the LTE/GTE bit to see if it matches.
216
* @return {string|undefined} The version number.
217
* @private
218
*/
219
goog.debug.DevCss.prototype.getVersionNumberFromSelectorText_ = function(
220
selectorText, userAgentToken) {
221
var regex = new RegExp(userAgentToken + '([\\d\\.]+)');
222
var matches = regex.exec(selectorText);
223
if (matches && matches.length == 2) {
224
return matches[1];
225
}
226
};
227
228
229
/**
230
* Extracts a rule version from the selector text, and if it finds one, calls
231
* compareVersions against it and the passed in token string to provide the
232
* value needed to determine if we have a match or not.
233
* @param {CSSRule} cssRule The rule to test against.
234
* @param {string} token The match token to test against the rule.
235
* @return {!Array|undefined} A tuple with the result of the compareVersions
236
* call and the matched ruleVersion.
237
* @private
238
*/
239
goog.debug.DevCss.prototype.getRuleVersionAndCompare_ = function(
240
cssRule, token) {
241
if (!cssRule.selectorText || !cssRule.selectorText.match(token)) {
242
return;
243
}
244
var ruleVersion =
245
this.getVersionNumberFromSelectorText_(cssRule.selectorText, token);
246
if (!ruleVersion) {
247
return;
248
}
249
250
var comparison =
251
goog.string.compareVersions(this.userAgentVersion_, ruleVersion);
252
return [comparison, ruleVersion];
253
};
254
255
256
/**
257
* Replaces a CSS selector if we have matches based on our useragent/version.
258
* Example: With a selector like ".USERAGENT-IE-LTE6 .class { prop: value }" if
259
* we are running IE6 we'll end up with ".class { prop: value }", thereby
260
* "activating" the selector.
261
* @param {CSSRule} cssRule The cssRule to potentially replace.
262
* @private
263
*/
264
goog.debug.DevCss.prototype.replaceBrowserSpecificClassNames_ = function(
265
cssRule) {
266
267
// If we don't match the browser token, we can stop now.
268
if (!cssRule.selectorText ||
269
!cssRule.selectorText.match(this.userAgentTokens_.ANY)) {
270
return;
271
}
272
273
// We know it will begin as a classname.
274
var additionalRegexString;
275
276
// Tests "Less than or equals".
277
var compared = this.getRuleVersionAndCompare_(
278
cssRule, this.userAgentTokens_.LESS_THAN_OR_EQUAL);
279
if (compared && compared.length) {
280
if (compared[0] > 0) {
281
return;
282
}
283
additionalRegexString =
284
this.userAgentTokens_.LESS_THAN_OR_EQUAL + compared[1];
285
}
286
287
// Tests "Less than".
288
compared =
289
this.getRuleVersionAndCompare_(cssRule, this.userAgentTokens_.LESS_THAN);
290
if (compared && compared.length) {
291
if (compared[0] > -1) {
292
return;
293
}
294
additionalRegexString = this.userAgentTokens_.LESS_THAN + compared[1];
295
}
296
297
// Tests "Greater than or equals".
298
compared = this.getRuleVersionAndCompare_(
299
cssRule, this.userAgentTokens_.GREATER_THAN_OR_EQUAL);
300
if (compared && compared.length) {
301
if (compared[0] < 0) {
302
return;
303
}
304
additionalRegexString =
305
this.userAgentTokens_.GREATER_THAN_OR_EQUAL + compared[1];
306
}
307
308
// Tests "Greater than".
309
compared = this.getRuleVersionAndCompare_(
310
cssRule, this.userAgentTokens_.GREATER_THAN);
311
if (compared && compared.length) {
312
if (compared[0] < 1) {
313
return;
314
}
315
additionalRegexString = this.userAgentTokens_.GREATER_THAN + compared[1];
316
}
317
318
// Tests "Equals".
319
compared =
320
this.getRuleVersionAndCompare_(cssRule, this.userAgentTokens_.EQUALS);
321
if (compared && compared.length) {
322
if (compared[0] != 0) {
323
return;
324
}
325
additionalRegexString = this.userAgentTokens_.EQUALS + compared[1];
326
}
327
328
// If we got to here without generating the additionalRegexString, then
329
// we did not match any of our comparison token strings, and we want a
330
// general browser token replacement.
331
if (!additionalRegexString) {
332
additionalRegexString = this.userAgentTokens_.ANY;
333
}
334
335
// We need to match at least a single whitespace character to know that
336
// we are matching the entire useragent string token.
337
var regexString = '\\.' + additionalRegexString + '\\s+';
338
var re = new RegExp(regexString, 'g');
339
340
var currentCssText = goog.cssom.getCssTextFromCssRule(cssRule);
341
342
// Replacing the token with '' activates the selector for this useragent.
343
var newCssText = currentCssText.replace(re, '');
344
345
if (newCssText != currentCssText) {
346
goog.cssom.replaceCssRule(cssRule, newCssText);
347
}
348
};
349
350
351
/**
352
* Replaces IE6 combined selector rules with a workable development alternative.
353
* IE6 actually parses .class1.class2 {} to simply .class2 {} which is nasty.
354
* To fully support combined selectors in IE6 this function needs to be paired
355
* with a call to replace the relevant DOM elements classNames as well.
356
* @see {this.addIe6CombinedClassNames_}
357
* @param {CSSRule} cssRule The rule to potentially fix.
358
* @private
359
*/
360
goog.debug.DevCss.prototype.replaceIe6CombinedSelectors_ = function(cssRule) {
361
// This match only ever works in IE because other UA's won't have our
362
// IE6_SELECTOR_TEXT in the cssText property.
363
if (cssRule.style && cssRule.style.cssText &&
364
cssRule.style.cssText.match(
365
goog.debug.DevCss.CssToken_.IE6_SELECTOR_TEXT)) {
366
var cssText = goog.cssom.getCssTextFromCssRule(cssRule);
367
var combinedSelectorText = this.getIe6CombinedSelectorText_(cssText);
368
if (combinedSelectorText) {
369
var newCssText = combinedSelectorText + '{' + cssRule.style.cssText + '}';
370
goog.cssom.replaceCssRule(cssRule, newCssText);
371
}
372
}
373
};
374
375
376
/**
377
* Gets the appropriate new combined selector text for IE6.
378
* Also adds an entry onto ie6CombinedMatches_ with relevant info for the
379
* likely following call to walk the DOM and rewrite the class attribute.
380
* Example: With a selector like
381
* ".class2 { -goog-ie6-selector: .class1.class2; prop: value }".
382
* this function will return:
383
* ".class1_class2 { prop: value }".
384
* @param {string} cssText The CSS selector text and css rule text combined.
385
* @return {?string} The rewritten css rule text.
386
* @private
387
*/
388
goog.debug.DevCss.prototype.getIe6CombinedSelectorText_ = function(cssText) {
389
var regex = new RegExp(
390
goog.debug.DevCss.CssToken_.IE6_SELECTOR_TEXT +
391
'\\s*:\\s*\\"([^\\"]+)\\"',
392
'gi');
393
var matches = regex.exec(cssText);
394
if (matches) {
395
var combinedSelectorText = matches[1];
396
// To aid in later fixing the DOM, we need to split up the possible
397
// selector groups by commas.
398
var groupedSelectors = combinedSelectorText.split(/\s*\,\s*/);
399
for (var i = 0, selector; selector = groupedSelectors[i]; i++) {
400
// Strips off the leading ".".
401
var combinedClassName = selector.substr(1);
402
var classNames = combinedClassName.split(
403
goog.debug.DevCss.CssToken_.IE6_COMBINED_GLUE);
404
var entry = {
405
classNames: classNames,
406
combinedClassName: combinedClassName,
407
els: []
408
};
409
this.ie6CombinedMatches_.push(entry);
410
}
411
return combinedSelectorText;
412
}
413
return null;
414
};
415
416
417
/**
418
* Adds combined selectors with underscores to make them "work" in IE6.
419
* @see {this.replaceIe6CombinedSelectors_}
420
* @private
421
*/
422
goog.debug.DevCss.prototype.addIe6CombinedClassNames_ = function() {
423
if (!this.ie6CombinedMatches_.length) {
424
return;
425
}
426
var allEls = document.getElementsByTagName('*');
427
// Match nodes for all classNames.
428
for (var i = 0, classNameEntry; classNameEntry = this.ie6CombinedMatches_[i];
429
i++) {
430
for (var j = 0, el; el = allEls[j]; j++) {
431
var classNamesLength = classNameEntry.classNames.length;
432
for (var k = 0, className; className = classNameEntry.classNames[k];
433
k++) {
434
if (!goog.dom.classlist.contains(el, className)) {
435
break;
436
}
437
if (k == classNamesLength - 1) {
438
classNameEntry.els.push(el);
439
}
440
}
441
}
442
// Walks over our matching nodes and fixes them.
443
if (classNameEntry.els.length) {
444
for (var j = 0, el; el = classNameEntry.els[j]; j++) {
445
goog.asserts.assert(el);
446
if (!goog.dom.classlist.contains(
447
el, classNameEntry.combinedClassName)) {
448
goog.dom.classlist.add(el, classNameEntry.combinedClassName);
449
}
450
}
451
}
452
}
453
};
454
455