Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(101)

Side by Side Diff: Source/core/css/CSSSelector.cpp

Issue 603443002: Don't add '(' to FUNCTION token names (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@internalsetshadowunused
Patch Set: Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « Source/core/css/CSSSelector.h ('k') | Source/core/css/parser/BisonCSSParser-in.cpp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) 2 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
3 * 1999 Waldo Bastian (bastian@kde.org) 3 * 1999 Waldo Bastian (bastian@kde.org)
4 * 2001 Andreas Schlapbach (schlpbch@iam.unibe.ch) 4 * 2001 Andreas Schlapbach (schlpbch@iam.unibe.ch)
5 * 2001-2003 Dirk Mueller (mueller@kde.org) 5 * 2001-2003 Dirk Mueller (mueller@kde.org)
6 * Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserv ed. 6 * Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserv ed.
7 * Copyright (C) 2008 David Smith (catfish.man@gmail.com) 7 * Copyright (C) 2008 David Smith (catfish.man@gmail.com)
8 * Copyright (C) 2010 Google Inc. All rights reserved. 8 * Copyright (C) 2010 Google Inc. All rights reserved.
9 * 9 *
10 * This library is free software; you can redistribute it and/or 10 * This library is free software; you can redistribute it and/or
(...skipping 259 matching lines...) Expand 10 before | Expand all | Expand 10 after
270 } 270 }
271 271
272 // Could be made smaller and faster by replacing pointer with an 272 // Could be made smaller and faster by replacing pointer with an
273 // offset into a string buffer and making the bit fields smaller but 273 // offset into a string buffer and making the bit fields smaller but
274 // that could not be maintained by hand. 274 // that could not be maintained by hand.
275 struct NameToPseudoStruct { 275 struct NameToPseudoStruct {
276 const char* string; 276 const char* string;
277 unsigned type:8; 277 unsigned type:8;
278 }; 278 };
279 279
280 // This table should be kept sorted. 280 // These tables should be kept sorted.
281 const static NameToPseudoStruct pseudoTypeMap[] = { 281 const static NameToPseudoStruct pseudoTypeWithoutArgumentsMap[] = {
282 {"-internal-list-box", CSSSelector::PseudoListBox}, 282 {"-internal-list-box", CSSSelector::PseudoListBox},
283 {"-internal-media-controls-cast-button", CSSSelector::PseudoWebKitCustomElement} , 283 {"-internal-media-controls-cast-button", CSSSelector::PseudoWebKitCustomElement} ,
284 {"-internal-media-controls-overlay-cast-button", CSSSelector::PseudoWebKitCustom Element}, 284 {"-internal-media-controls-overlay-cast-button", CSSSelector::PseudoWebKitCustom Element},
285 {"-internal-spatial-navigation-focus", CSSSelector::PseudoSpatialNavigationFocus }, 285 {"-internal-spatial-navigation-focus", CSSSelector::PseudoSpatialNavigationFocus },
286 {"-webkit-any(", CSSSelector::PseudoAny},
287 {"-webkit-any-link", CSSSelector::PseudoAnyLink}, 286 {"-webkit-any-link", CSSSelector::PseudoAnyLink},
288 {"-webkit-autofill", CSSSelector::PseudoAutofill}, 287 {"-webkit-autofill", CSSSelector::PseudoAutofill},
289 {"-webkit-drag", CSSSelector::PseudoDrag}, 288 {"-webkit-drag", CSSSelector::PseudoDrag},
290 {"-webkit-full-page-media", CSSSelector::PseudoFullPageMedia}, 289 {"-webkit-full-page-media", CSSSelector::PseudoFullPageMedia},
291 {"-webkit-full-screen", CSSSelector::PseudoFullScreen}, 290 {"-webkit-full-screen", CSSSelector::PseudoFullScreen},
292 {"-webkit-full-screen-ancestor", CSSSelector::PseudoFullScreenAncestor}, 291 {"-webkit-full-screen-ancestor", CSSSelector::PseudoFullScreenAncestor},
293 {"-webkit-full-screen-document", CSSSelector::PseudoFullScreenDocument}, 292 {"-webkit-full-screen-document", CSSSelector::PseudoFullScreenDocument},
294 {"-webkit-resizer", CSSSelector::PseudoResizer}, 293 {"-webkit-resizer", CSSSelector::PseudoResizer},
295 {"-webkit-scrollbar", CSSSelector::PseudoScrollbar}, 294 {"-webkit-scrollbar", CSSSelector::PseudoScrollbar},
296 {"-webkit-scrollbar-button", CSSSelector::PseudoScrollbarButton}, 295 {"-webkit-scrollbar-button", CSSSelector::PseudoScrollbarButton},
297 {"-webkit-scrollbar-corner", CSSSelector::PseudoScrollbarCorner}, 296 {"-webkit-scrollbar-corner", CSSSelector::PseudoScrollbarCorner},
298 {"-webkit-scrollbar-thumb", CSSSelector::PseudoScrollbarThumb}, 297 {"-webkit-scrollbar-thumb", CSSSelector::PseudoScrollbarThumb},
299 {"-webkit-scrollbar-track", CSSSelector::PseudoScrollbarTrack}, 298 {"-webkit-scrollbar-track", CSSSelector::PseudoScrollbarTrack},
300 {"-webkit-scrollbar-track-piece", CSSSelector::PseudoScrollbarTrackPiece}, 299 {"-webkit-scrollbar-track-piece", CSSSelector::PseudoScrollbarTrackPiece},
301 {"active", CSSSelector::PseudoActive}, 300 {"active", CSSSelector::PseudoActive},
302 {"after", CSSSelector::PseudoAfter}, 301 {"after", CSSSelector::PseudoAfter},
303 {"backdrop", CSSSelector::PseudoBackdrop}, 302 {"backdrop", CSSSelector::PseudoBackdrop},
304 {"before", CSSSelector::PseudoBefore}, 303 {"before", CSSSelector::PseudoBefore},
305 {"checked", CSSSelector::PseudoChecked}, 304 {"checked", CSSSelector::PseudoChecked},
306 {"content", CSSSelector::PseudoContent}, 305 {"content", CSSSelector::PseudoContent},
307 {"corner-present", CSSSelector::PseudoCornerPresent}, 306 {"corner-present", CSSSelector::PseudoCornerPresent},
308 {"cue", CSSSelector::PseudoWebKitCustomElement}, 307 {"cue", CSSSelector::PseudoWebKitCustomElement},
309 {"cue(", CSSSelector::PseudoCue},
310 {"decrement", CSSSelector::PseudoDecrement}, 308 {"decrement", CSSSelector::PseudoDecrement},
311 {"default", CSSSelector::PseudoDefault}, 309 {"default", CSSSelector::PseudoDefault},
312 {"disabled", CSSSelector::PseudoDisabled}, 310 {"disabled", CSSSelector::PseudoDisabled},
313 {"double-button", CSSSelector::PseudoDoubleButton}, 311 {"double-button", CSSSelector::PseudoDoubleButton},
314 {"empty", CSSSelector::PseudoEmpty}, 312 {"empty", CSSSelector::PseudoEmpty},
315 {"enabled", CSSSelector::PseudoEnabled}, 313 {"enabled", CSSSelector::PseudoEnabled},
316 {"end", CSSSelector::PseudoEnd}, 314 {"end", CSSSelector::PseudoEnd},
317 {"first", CSSSelector::PseudoFirstPage}, 315 {"first", CSSSelector::PseudoFirstPage},
318 {"first-child", CSSSelector::PseudoFirstChild}, 316 {"first-child", CSSSelector::PseudoFirstChild},
319 {"first-letter", CSSSelector::PseudoFirstLetter}, 317 {"first-letter", CSSSelector::PseudoFirstLetter},
320 {"first-line", CSSSelector::PseudoFirstLine}, 318 {"first-line", CSSSelector::PseudoFirstLine},
321 {"first-of-type", CSSSelector::PseudoFirstOfType}, 319 {"first-of-type", CSSSelector::PseudoFirstOfType},
322 {"focus", CSSSelector::PseudoFocus}, 320 {"focus", CSSSelector::PseudoFocus},
323 {"future", CSSSelector::PseudoFutureCue}, 321 {"future", CSSSelector::PseudoFutureCue},
324 {"horizontal", CSSSelector::PseudoHorizontal}, 322 {"horizontal", CSSSelector::PseudoHorizontal},
325 {"host", CSSSelector::PseudoHost}, 323 {"host", CSSSelector::PseudoHost},
326 {"host(", CSSSelector::PseudoHost},
327 {"host-context(", CSSSelector::PseudoHostContext},
328 {"hover", CSSSelector::PseudoHover}, 324 {"hover", CSSSelector::PseudoHover},
329 {"in-range", CSSSelector::PseudoInRange}, 325 {"in-range", CSSSelector::PseudoInRange},
330 {"increment", CSSSelector::PseudoIncrement}, 326 {"increment", CSSSelector::PseudoIncrement},
331 {"indeterminate", CSSSelector::PseudoIndeterminate}, 327 {"indeterminate", CSSSelector::PseudoIndeterminate},
332 {"invalid", CSSSelector::PseudoInvalid}, 328 {"invalid", CSSSelector::PseudoInvalid},
333 {"lang(", CSSSelector::PseudoLang},
334 {"last-child", CSSSelector::PseudoLastChild}, 329 {"last-child", CSSSelector::PseudoLastChild},
335 {"last-of-type", CSSSelector::PseudoLastOfType}, 330 {"last-of-type", CSSSelector::PseudoLastOfType},
336 {"left", CSSSelector::PseudoLeftPage}, 331 {"left", CSSSelector::PseudoLeftPage},
337 {"link", CSSSelector::PseudoLink}, 332 {"link", CSSSelector::PseudoLink},
338 {"no-button", CSSSelector::PseudoNoButton}, 333 {"no-button", CSSSelector::PseudoNoButton},
339 {"not(", CSSSelector::PseudoNot},
340 {"nth-child(", CSSSelector::PseudoNthChild},
341 {"nth-last-child(", CSSSelector::PseudoNthLastChild},
342 {"nth-last-of-type(", CSSSelector::PseudoNthLastOfType},
343 {"nth-of-type(", CSSSelector::PseudoNthOfType},
344 {"only-child", CSSSelector::PseudoOnlyChild}, 334 {"only-child", CSSSelector::PseudoOnlyChild},
345 {"only-of-type", CSSSelector::PseudoOnlyOfType}, 335 {"only-of-type", CSSSelector::PseudoOnlyOfType},
346 {"optional", CSSSelector::PseudoOptional}, 336 {"optional", CSSSelector::PseudoOptional},
347 {"out-of-range", CSSSelector::PseudoOutOfRange}, 337 {"out-of-range", CSSSelector::PseudoOutOfRange},
348 {"past", CSSSelector::PseudoPastCue}, 338 {"past", CSSSelector::PseudoPastCue},
349 {"read-only", CSSSelector::PseudoReadOnly}, 339 {"read-only", CSSSelector::PseudoReadOnly},
350 {"read-write", CSSSelector::PseudoReadWrite}, 340 {"read-write", CSSSelector::PseudoReadWrite},
351 {"required", CSSSelector::PseudoRequired}, 341 {"required", CSSSelector::PseudoRequired},
352 {"right", CSSSelector::PseudoRightPage}, 342 {"right", CSSSelector::PseudoRightPage},
353 {"root", CSSSelector::PseudoRoot}, 343 {"root", CSSSelector::PseudoRoot},
354 {"scope", CSSSelector::PseudoScope}, 344 {"scope", CSSSelector::PseudoScope},
355 {"selection", CSSSelector::PseudoSelection}, 345 {"selection", CSSSelector::PseudoSelection},
356 {"shadow", CSSSelector::PseudoShadow}, 346 {"shadow", CSSSelector::PseudoShadow},
357 {"single-button", CSSSelector::PseudoSingleButton}, 347 {"single-button", CSSSelector::PseudoSingleButton},
358 {"start", CSSSelector::PseudoStart}, 348 {"start", CSSSelector::PseudoStart},
359 {"target", CSSSelector::PseudoTarget}, 349 {"target", CSSSelector::PseudoTarget},
360 {"unresolved", CSSSelector::PseudoUnresolved}, 350 {"unresolved", CSSSelector::PseudoUnresolved},
361 {"valid", CSSSelector::PseudoValid}, 351 {"valid", CSSSelector::PseudoValid},
362 {"vertical", CSSSelector::PseudoVertical}, 352 {"vertical", CSSSelector::PseudoVertical},
363 {"visited", CSSSelector::PseudoVisited}, 353 {"visited", CSSSelector::PseudoVisited},
364 {"window-inactive", CSSSelector::PseudoWindowInactive}, 354 {"window-inactive", CSSSelector::PseudoWindowInactive},
365 }; 355 };
366 356
357 const static NameToPseudoStruct pseudoTypeWithArgumentsMap[] = {
358 {"-webkit-any", CSSSelector::PseudoAny},
359 {"cue", CSSSelector::PseudoCue},
360 {"host", CSSSelector::PseudoHost},
361 {"host-context", CSSSelector::PseudoHostContext},
362 {"lang", CSSSelector::PseudoLang},
363 {"not", CSSSelector::PseudoNot},
364 {"nth-child", CSSSelector::PseudoNthChild},
365 {"nth-last-child", CSSSelector::PseudoNthLastChild},
366 {"nth-last-of-type", CSSSelector::PseudoNthLastOfType},
367 {"nth-of-type", CSSSelector::PseudoNthOfType},
368 };
369
367 class NameToPseudoCompare { 370 class NameToPseudoCompare {
368 public: 371 public:
369 NameToPseudoCompare(const AtomicString& key) : m_key(key) { ASSERT(m_key.is8 Bit()); } 372 NameToPseudoCompare(const AtomicString& key) : m_key(key) { ASSERT(m_key.is8 Bit()); }
370 373
371 bool operator()(const NameToPseudoStruct& entry, const NameToPseudoStruct&) 374 bool operator()(const NameToPseudoStruct& entry, const NameToPseudoStruct&)
372 { 375 {
373 ASSERT(entry.string); 376 ASSERT(entry.string);
374 const char* key = reinterpret_cast<const char*>(m_key.characters8()); 377 const char* key = reinterpret_cast<const char*>(m_key.characters8());
375 // If strncmp returns 0, then either the keys are equal, or |m_key| sort s before |entry|. 378 // If strncmp returns 0, then either the keys are equal, or |m_key| sort s before |entry|.
376 return strncmp(entry.string, key, m_key.length()) < 0; 379 return strncmp(entry.string, key, m_key.length()) < 0;
377 } 380 }
378 381
379 private: 382 private:
380 const AtomicString& m_key; 383 const AtomicString& m_key;
381 }; 384 };
382 385
383 static CSSSelector::PseudoType nameToPseudoType(const AtomicString& name) 386 static CSSSelector::PseudoType nameToPseudoType(const AtomicString& name, bool h asArguments)
384 { 387 {
385 if (name.isNull() || !name.is8Bit()) 388 if (name.isNull() || !name.is8Bit())
386 return CSSSelector::PseudoUnknown; 389 return CSSSelector::PseudoUnknown;
387 390
388 const NameToPseudoStruct* pseudoTypeMapEnd = pseudoTypeMap + WTF_ARRAY_LENGT H(pseudoTypeMap); 391 const NameToPseudoStruct* pseudoTypeMap;
392 const NameToPseudoStruct* pseudoTypeMapEnd;
393 if (hasArguments) {
394 pseudoTypeMap = pseudoTypeWithArgumentsMap;
395 pseudoTypeMapEnd = pseudoTypeWithArgumentsMap + WTF_ARRAY_LENGTH(pseudoT ypeWithArgumentsMap);
396 } else {
397 pseudoTypeMap = pseudoTypeWithoutArgumentsMap;
398 pseudoTypeMapEnd = pseudoTypeWithoutArgumentsMap + WTF_ARRAY_LENGTH(pseu doTypeWithoutArgumentsMap);
399 }
389 NameToPseudoStruct dummyKey = { 0, CSSSelector::PseudoUnknown }; 400 NameToPseudoStruct dummyKey = { 0, CSSSelector::PseudoUnknown };
390 const NameToPseudoStruct* match = std::lower_bound(pseudoTypeMap, pseudoType MapEnd, dummyKey, NameToPseudoCompare(name)); 401 const NameToPseudoStruct* match = std::lower_bound(pseudoTypeMap, pseudoType MapEnd, dummyKey, NameToPseudoCompare(name));
391 if (match == pseudoTypeMapEnd || match->string != name.string()) 402 if (match == pseudoTypeMapEnd || match->string != name.string())
392 return CSSSelector::PseudoUnknown; 403 return CSSSelector::PseudoUnknown;
393 404
394 return static_cast<CSSSelector::PseudoType>(match->type); 405 return static_cast<CSSSelector::PseudoType>(match->type);
395 } 406 }
396 407
397 #ifndef NDEBUG 408 #ifndef NDEBUG
398 void CSSSelector::show(int indent) const 409 void CSSSelector::show(int indent) const
(...skipping 20 matching lines...) Expand all
419 } 430 }
420 431
421 void CSSSelector::show() const 432 void CSSSelector::show() const
422 { 433 {
423 printf("\n******* CSSSelector::show(\"%s\") *******\n", selectorText().ascii ().data()); 434 printf("\n******* CSSSelector::show(\"%s\") *******\n", selectorText().ascii ().data());
424 show(2); 435 show(2);
425 printf("******* end *******\n"); 436 printf("******* end *******\n");
426 } 437 }
427 #endif 438 #endif
428 439
429 CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name) 440 CSSSelector::PseudoType CSSSelector::parsePseudoType(const AtomicString& name, b ool hasArguments)
430 { 441 {
431 CSSSelector::PseudoType pseudoType = nameToPseudoType(name); 442 CSSSelector::PseudoType pseudoType = nameToPseudoType(name, hasArguments);
432 if (pseudoType != PseudoUnknown) 443 if (pseudoType != PseudoUnknown)
433 return pseudoType; 444 return pseudoType;
434 445
435 if (name.startsWith("-webkit-")) 446 if (name.startsWith("-webkit-"))
436 return PseudoWebKitCustomElement; 447 return PseudoWebKitCustomElement;
437 if (name.startsWith("cue")) 448 if (name.startsWith("cue"))
438 return PseudoUserAgentCustomElement; 449 return PseudoUserAgentCustomElement;
439 450
440 return PseudoUnknown; 451 return PseudoUnknown;
441 } 452 }
442 453
443 void CSSSelector::extractPseudoType() const 454 void CSSSelector::extractPseudoType() const
444 { 455 {
445 if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePse udoClass) 456 if (m_match != PseudoClass && m_match != PseudoElement && m_match != PagePse udoClass)
446 return; 457 return;
447 458
448 m_pseudoType = parsePseudoType(value()); 459 m_pseudoType = parsePseudoType(value(), !argument().isNull() || selectorList ());
449 460
450 bool element = false; // pseudo-element 461 bool element = false; // pseudo-element
451 bool compat = false; // single colon compatbility mode 462 bool compat = false; // single colon compatbility mode
452 bool isPagePseudoClass = false; // Page pseudo-class 463 bool isPagePseudoClass = false; // Page pseudo-class
453 464
454 switch (m_pseudoType) { 465 switch (m_pseudoType) {
455 case PseudoAfter: 466 case PseudoAfter:
456 case PseudoBefore: 467 case PseudoBefore:
457 case PseudoCue: 468 case PseudoCue:
458 case PseudoFirstLetter: 469 case PseudoFirstLetter:
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after
607 } else if (cs->m_match == CSSSelector::Class) { 618 } else if (cs->m_match == CSSSelector::Class) {
608 str.append('.'); 619 str.append('.');
609 serializeIdentifier(cs->value(), str); 620 serializeIdentifier(cs->value(), str);
610 } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSS Selector::PagePseudoClass) { 621 } else if (cs->m_match == CSSSelector::PseudoClass || cs->m_match == CSS Selector::PagePseudoClass) {
611 str.append(':'); 622 str.append(':');
612 str.append(cs->value()); 623 str.append(cs->value());
613 624
614 switch (cs->pseudoType()) { 625 switch (cs->pseudoType()) {
615 case PseudoNot: 626 case PseudoNot:
616 ASSERT(cs->selectorList()); 627 ASSERT(cs->selectorList());
628 str.append('(');
617 str.append(cs->selectorList()->first()->selectorText()); 629 str.append(cs->selectorList()->first()->selectorText());
618 str.append(')'); 630 str.append(')');
619 break; 631 break;
620 case PseudoLang: 632 case PseudoLang:
621 case PseudoNthChild: 633 case PseudoNthChild:
622 case PseudoNthLastChild: 634 case PseudoNthLastChild:
623 case PseudoNthOfType: 635 case PseudoNthOfType:
624 case PseudoNthLastOfType: 636 case PseudoNthLastOfType:
637 str.append('(');
625 str.append(cs->argument()); 638 str.append(cs->argument());
626 str.append(')'); 639 str.append(')');
627 break; 640 break;
628 case PseudoAny: { 641 case PseudoAny: {
642 str.append('(');
629 const CSSSelector* firstSubSelector = cs->selectorList()->first( ); 643 const CSSSelector* firstSubSelector = cs->selectorList()->first( );
630 for (const CSSSelector* subSelector = firstSubSelector; subSelec tor; subSelector = CSSSelectorList::next(*subSelector)) { 644 for (const CSSSelector* subSelector = firstSubSelector; subSelec tor; subSelector = CSSSelectorList::next(*subSelector)) {
631 if (subSelector != firstSubSelector) 645 if (subSelector != firstSubSelector)
632 str.append(','); 646 str.append(',');
633 str.append(subSelector->selectorText()); 647 str.append(subSelector->selectorText());
634 } 648 }
635 str.append(')'); 649 str.append(')');
636 break; 650 break;
637 } 651 }
638 case PseudoHost: 652 case PseudoHost:
639 case PseudoHostContext: { 653 case PseudoHostContext: {
640 if (cs->selectorList()) { 654 if (cs->selectorList()) {
655 str.append('(');
641 const CSSSelector* firstSubSelector = cs->selectorList()->fi rst(); 656 const CSSSelector* firstSubSelector = cs->selectorList()->fi rst();
642 for (const CSSSelector* subSelector = firstSubSelector; subS elector; subSelector = CSSSelectorList::next(*subSelector)) { 657 for (const CSSSelector* subSelector = firstSubSelector; subS elector; subSelector = CSSSelectorList::next(*subSelector)) {
643 if (subSelector != firstSubSelector) 658 if (subSelector != firstSubSelector)
644 str.append(','); 659 str.append(',');
645 str.append(subSelector->selectorText()); 660 str.append(subSelector->selectorText());
646 } 661 }
647 str.append(')'); 662 str.append(')');
648 } 663 }
649 break; 664 break;
650 } 665 }
(...skipping 250 matching lines...) Expand 10 before | Expand all | Expand 10 after
901 if (count < nthBValue()) 916 if (count < nthBValue())
902 return false; 917 return false;
903 return (count - nthBValue()) % nthAValue() == 0; 918 return (count - nthBValue()) % nthAValue() == 0;
904 } 919 }
905 if (count > nthBValue()) 920 if (count > nthBValue())
906 return false; 921 return false;
907 return (nthBValue() - count) % (-nthAValue()) == 0; 922 return (nthBValue() - count) % (-nthAValue()) == 0;
908 } 923 }
909 924
910 } // namespace blink 925 } // namespace blink
OLDNEW
« no previous file with comments | « Source/core/css/CSSSelector.h ('k') | Source/core/css/parser/BisonCSSParser-in.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698