Added: trunk/ManualTests/resources/empty-text.txt ( => )
Added: trunk/ManualTests/resources/full_results.json
===================================================================
--- trunk/ManualTests/resources/full_results.json (rev 0)
+++ trunk/ManualTests/resources/full_results.json 2021-02-01 21:40:55 UTC (rev 272174)
@@ -0,0 +1 @@
+ADD_RESULTS({"tests":{"platform":{"mac":{"fast":{"text":{"combining-character-sequence-fallback.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"ligature-subdivision.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"vertical-no-sideways.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"systemFont.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true}},"overflow":{"overflow-scrollbar-hit-test.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"objc":{"longlongTest.html":{"report":"REGRESSION","expected"
;:"PASS","actual":"TEXT","has_stderr":true},"webScriptObject-hasWebScriptKey.html":{"report":"REGRESSION","expected":"PASS","actual":"TEXT","has_stderr":true}},"loader":{"file-url-mimetypes-2.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}}}}},"webgl":{"2.0.0":{"conformance":{"textures":{"misc":{"texture-copying-feedback-loops.html":{"report":"REGRESSION","expected":"PASS","actual":"CRASH","has_stderr":true}}}}},"1.0.3":{"conformance":{"textures":{"texture-copying-feedback-loops.html":{"report":"REGRESSION","expected":"PASS","actual":"CRASH","has_stderr&
quot;:true}}}}},"fast":{"css-generated-content":{"initial-letter-first-line-wrapping.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.93}},"text":{"arabic-zwj-and-zwnj.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"font-variations-feature-detection.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":12.16},"emphasis-vertical.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"emoji-gender-6.html":{"expected":"IMAGE","actual":"IMAGE",
"has_stderr":true,"reftest_type":["=="],"image_diff_percent":8.52},"multiglyph-characters.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"emoji-gender-9.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.37},"emoji-gender-3.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":4.17},"text-security-disc-bullet-pua.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.37},"letter-spacing-shaping.html":{"expected":"PASS IMAGE"
,"actual":"PASS","has_stderr":true,"reftest_type":["=="]},"arabic-blocked-fonts.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"system-font-japanese-synthetic-italic.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"user-installed-fonts":{"extended-character.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"shadow-family.html":{"expected":"PASS IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.69},"shadow-postscript-family.html":{"e
xpected":"PASS IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.69},"shadow-disable.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"shadow-family-disable.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"local.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"shadow-postscript.html":{"expected":"PASS IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.69},"extended-character-with-user-font.ht
ml":{"expected":"PASS IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["!="]},"shadow-postscript-disable.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"disable.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"shadow-postscript-family-disable.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"system-ui.html":{"expected":"PASS IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["!="]},"shadow.html":{"expected":"PASS IMAGE","actual
":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.69}},"locale-shaping.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"woff2-totalsfntsize.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"canvas-color-fonts":{"stroke-color-shadow-COLR.html":{"expected":"PASS","actual":"IMAGE","report":"REGRESSION","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.31},"stroke-color-COLR.html":{"expected":"PASS","actual":"IMAGE","report":"REGRESSION","has_stderr":true,"refte
st_type":["=="],"image_diff_percent":0.31}},"hyphen-min-preferred-width.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01},"emoji-gender-4.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":3.6},"mac":{"select-character-before-zero-width-joiner.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]}},"international":{"bidi-AN-after-empty-run.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"bidi-CS-after-AN.html":{"expected":"PASS FAIL",&
quot;actual":"TEXT","has_stderr":true},"system-language":{"composition.html":{"expected":"PASS FAIL IMAGE","actual":"PASS","has_stderr":true},"system-font-punctuation.html":{"expected":"PASS FAIL IMAGE","actual":"PASS","has_stderr":true},"navigator-language":{"navigator-language-fr-CA.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-es-MX.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-zh-Hant-HK.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-hi.html":{"expected":"PASS FAIL IMAGE","a
ctual":"TEXT","has_stderr":true},"navigator-language-fr.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-zh-TW.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-zh-Hans.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-es-ES.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-zh-HK.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-pt-PT.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true
},"navigator-language-zh-Hant.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-en-GB.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-ja.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-en.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-en-US.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-ru.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-pt-BR.html":{"expected&qu
ot;:"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-es-419.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"navigator-language-es.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true}},"declarative-language.html":{"expected":"PASS FAIL IMAGE","actual":"TEXT","has_stderr":true},"arabic-glyph-cache-fill-combine.html":{"expected":"PASS FAIL IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"han-quotes.html":{"expected":"PASS FAIL IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["!="]},"hindi-system-font-punctuation.html&q
uot;:{"expected":"PASS FAIL IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]}},"thai-line-breaks.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true}},"emoji-gender-5.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":5.0},"letterpress-different.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["!="]},"crash-complex-text-surrogate.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"emoji-gender-8.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":t
rue,"reftest_type":["=="],"image_diff_percent":2.71},"chinese-font-name-aliases-2.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":1.86},"woff2.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"control-characters":{"visible-control-characters-3.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["!="]},"visible-control-characters-3.xhtml":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["!="]},"visible-control-characters-2.xhtml":{"expected":"IMAGE",&q
uot;actual":"IMAGE","has_stderr":true,"reftest_type":["!="]},"visible-control-characters-2.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["!="]},"visible-control-characters-1.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["!="]}}},"parser":{"xml-error-adopted.xml":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true},"adoption-agency-unload-iframe-4.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true}},"dynamic":{"text-combine.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"loader"
;:{"_javascript_-url-in-embed.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"redirect-to-invalid-url-using-meta-refresh-disallowed-async-delegates.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"document-with-fragment-url-4.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true},"redirect-to-invalid-url-using-meta-refresh-calls-policy-delegate.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"document-with-fragment-url-3.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true}},"frames":{"sandboxed-iframe-plugins.html":{"report":"REGRESSION","expected":"PASS&quo
t;,"actual":"TEXT","has_stderr":true},"crash-during-iframe-load-stop.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true},"flattening":{"frameset-flattening-subframesets.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"frameset-flattening-grid.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"iframe-flattening-fixed-height.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"iframe-flattening-out-of-view.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"iframe-flattening-offscreen.html":{"expected":"PASS FAIL","actual":"TEXT"
;,"has_stderr":true},"frameset-flattening-advanced.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true}},"sandboxed-iframe-about-blank.html":{"report":"REGRESSION","expected":"PASS","actual":"TEXT","has_stderr":true},"sandboxed-iframe-navigation-allowed.html":{"report":"REGRESSION","expected":"PASS","actual":"TEXT","has_stderr":true}},"harness":{"sample-fail-mismatch-reftest.html":{"expected":"IMAGE","wontfix":true,"actual":"IMAGE","has_stderr":true,"reftest_type":["!="]}},"css-intrinsic-dimensions":{"height-tables.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,&qu
ot;reftest_type":["=="],"image_diff_percent":0.02},"height-css-tables.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.04}},"images":{"async-image-body-background-image.html":{"expected":"PASS TIMEOUT","actual":"TIMEOUT","has_stderr":true,"reftest_type":["=="]},"animated-png-loop-count.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"animated-gif-loop-count.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"webp-as-image.html":{"expected":"PASS IMAGE","actua
l":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":24.99},"gif-loop-count.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"image-copy-memory-usage.html":{"expected":"FAIL","actual":"PASS","has_stderr":true},"animated-gif-no-layout.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":1.14},"slower-animation-than-decoding-image.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"animated-image-different-dest-size.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr&qu
ot;:true,"reftest_type":["=="]},"image-controls-basic.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"animated-gif-scrolling-crash.html":{"expected":"PASS TIMEOUT","actual":"TIMEOUT","has_stderr":true},"reset-image-animation.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true}},"table":{"hittest-tablecell-right-edge.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"border-collapsing":{"collapsed-borders-adjoining-sections.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.07}},"hittest-tablecell-with-borders-right-edge.h
tml":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true}},"layers":{"no-clipping-overflow-hidden-added-after-transform.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]}},"canvas":{"canvas-ellipse-zero-lineto.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"canvas-fillRect-gradient-shadow.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"canvas-composite-alpha.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"canvas-scale-strokePath-shadow.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"
;2d.text.draw.fill.maxWidth.gradient.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"canvas-quadratic-same-endpoint.html":{"expected":"FAIL","actual":"PASS","has_stderr":true},"canvas-draw-canvas-on-canvas-shadow.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"set-colors.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"canvas-currentColor.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"canvas-imageSmoothingEnabled-repaint.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"webgl":{"webgl-compressed-texture-size-limit.html":{"expected&qu
ot;:"PASS FAIL TIMEOUT","actual":"TEXT","has_stderr":true},"bufferData-offset-length.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"context-creation-attributes.html":{"report":"REGRESSION","expected":"PASS","actual":"TEXT","has_stderr":true},"tex-image-and-sub-image-2d-with-image-data-rgb565.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"tex-image-and-sub-image-2d-with-array-buffer-view.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"tex-image-and-sub-image-2d-with-image-data-rgba4444.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"webgl-draw-buffers.h
tml":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"read-pixels-test.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"tex-image-and-sub-image-2d-with-potentially-subsampled-image.html":{"expected":"CRASH","actual":"PASS","has_stderr":true},"program-test.html":{"expected":"FAIL","actual":"PASS","has_stderr":true},"tex-image-and-sub-image-2d-with-image-data-rgba5551.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"oes-element-index-uint.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"tex-image-and-sub-image-2d-with-image-data.html":{"expected":"PAS
S FAIL","actual":"PASS","has_stderr":true},"texImage2D-mse-flipY-false.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true},"oes-texture-float-linear.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true}}},"multicol":{"pagination":{"RightToLeft-tb.html":{"expected":"FAIL","actual":"PASS","has_stderr":true},"RightToLeft-rl.html":{"expected":"FAIL","actual":"PASS","has_stderr":true}},"multicol-with-child-renderLayer-for-input.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01},"crash-when-spanner-candidate-is-out-of-flo
w.html":{"report":"REGRESSION","expected":"PASS","actual":"CRASH","has_stderr":true},"fixed-stack.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01},"mixed-opacity-fixed-test.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":8.54}},"xmlhttprequest":{"xmlhttprequest-recursive-sync-event.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true}},"text-autosizing":{"ios":{"text-autosizing-after-back.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true,"
;reftest_type":["=="]}}},"ruby":{"base-shorter-than-text.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"workers":{"worker-terminate.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true}},"preloader":{"image-srcset.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true}},"forms":{"searchfield-heights.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"textarea":{"textarea-state-restore.html":{"expected":"PASS FAIL TIMEOUT","actual":"PASS","has_stderr":true}},"search-event-delay.html":{"expected":"PASS FAIL","actual":"PASS",&q
uot;has_stderr":true},"input-set-composition-scroll.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"focus-selection-textarea.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"state-restore-to-non-edited-controls.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"listbox-scrollbar-hit-test.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"layoutformattingcontext":{"run-expansion-extends-line-content-width.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.44},"quirk-margin-not-collapse-when-floating.html":{"expected&
quot;:"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.27},"inlin-box-content-fits-but-the-box-does-not.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.62},"br-and-wbr-simple.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.34},"intrusive-floats-takes-entire-horizontal-space.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.72},"horizontal-sizing-with-trailing-letter-spacing.html":{"expected":"IMAGE","actual&
quot;:"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.93},"wbr-simple.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.02}},"performance":{"performance-now-timestamps.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true}},"borders":{"border-painting-dotted.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":1.08},"border-painting-dashed.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.71},"
;border-painting-double.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.47},"border-fit.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"border-fit-2.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true}},"sub-pixel":{"block-with-margin-overflow.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.1},"client-width-height-snapping.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"file-upload-control-at-fractional-offset.html":{"expected":"IMAGE",&qu
ot;actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.02},"input-caret-on-subpixel-bound.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01}},"events":{"scroll-div-with-prevent-default-in-subframe.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"context-nodrag.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"attempt-scroll-with-no-scrollbars.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"tabindex-focus-blur-all.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":t
rue},"mouse-cursor-change.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"mouseout-on-window.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"constructors":{"close-event-constructor.html":{"report":"REGRESSION","expected":"PASS","actual":"TEXT","has_stderr":true},"overflow-event-constructor.html":{"report":"REGRESSION","expected":"PASS","actual":"TEXT","has_stderr":true}},"before-unload-adopt-subframe-to-outside.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true},"overflow-scroll-fake-mouse-move.html":{"expected":"PASS FAIL","actual":"PASS","has
_stderr":true}},"css":{"parsing-css-allowed-string-characters.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"object-fit":{"object-fit-canvas.html":{"expected":"PASS IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.04}},"vertical-align-baseline-rowspan-010.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.48},"sticky":{"inline-sticky-abspos-child.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"inline-sticky.html":{"expected":"PASS IMAGE","actual"
;:"PASS","has_stderr":true,"reftest_type":["=="]},"sticky-top-zoomed.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":13.56},"sticky-both-sides.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]}},"read-only-read-write-input-basics.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.35},"resize-corner-tracking.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"input-search-padding.html":{"expected":"FAIL","actual":"TEXT",&q
uot;has_stderr":true},"pseudo-visited-background-color-on-input.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.36},"counters":{"element-removal-crash.xhtml":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true}}},"hidpi":{"filters-and-image-buffer-resolution.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]}},"css-grid-layout":{"grid-shorthands-style-format.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"positioned-grid-container-percentage-tracks.html":{"expected":"FAIL","actual":"TEXT","has_stder
r":true},"grid-align-baseline-vertical.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"mediacapturefromelement":{"CanvasCaptureMediaStream-2d-events.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true}},"overflow":{"scrollbar-click-retains-focus.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"horizontal-scroll-after-back.html":{"expected":"PASS TIMEOUT","actual":"PASS","has_stderr":true}},"scrolling":{"rtl-scrollbars-simple.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["!="]},"rtl-scrollbars-positioning.html":{"expected":"IMAGE",&quo
t;actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.28},"rtl-scrollbars-animation-property.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"rtl-scrollbars-iframe-offset.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.18},"rtl-scrollbars.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.78},"rtl-scrollbars-text-selection.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01},"scrollable-area-fra
me-zero-size-and-border.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"rtl-scrollbars-position-absolute.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01},"rtl-scrollbars-elementFromPoint-static.html":{"expected":"FAIL","actual":"PASS","has_stderr":true},"rtl-scrollbars-positioned-intersect-scrollbars.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-overflow-elementFromPoint.html":{"expected":"FAIL","actual":"PASS","has_stderr":true},"rtl-scrollbars-iframe-scrolled.html":{"expected":"IMAGE"
;,"actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01},"rtl-scrollbars-listbox-simple.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"rtl-scrollbars-listbox-scroll.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-text-selection-scrolled.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01},"rtl-scrollbars-listbox-select-right.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-ov
erflow-contents.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-overflow-position-absolute.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-overflow-text-selection-scrolled.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-overflow.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-listbox-select-left.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollba
rs-overflow-simple.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["!="]},"rtl-scrollbars-overflow-padding.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-alternate-body-dir-attr-does-not-update-scrollbar-placement-2.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-position-fixed.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01},"rtl-scrollbars-overflow-dir-rtl.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true
,"reftest_type":["=="]},"rtl-scrollbars-listbox.html":{"expected":"IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"rtl-scrollbars-alternate-iframe-body-dir-attr-does-not-update-scrollbar-placement.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.58}},"animation":{"request-animation-frame-time-unit.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true}},"repaint":{"border-fit-lines.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"hidpi-wrong-repaint-rect-when-parent-has-noncompositing-transform.html":{"ex
pected":"FAIL","actual":"TEXT","has_stderr":true},"add-table-overpaint.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"list-item-equal-style-change-no-repaint.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"japanese-rl-selection-repaint.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"vertical-text-repaint.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"obscured-background-no-repaint.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"japanese-rl-selection-clear.html":{"expected":"FAIL","actual":"TEXT","has_stde
rr":true},"repaint-across-writing-mode-boundary.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"writing-mode":{"broken-ideograph-small-caps.html":{"expected":"FAIL IMAGE","actual":"TEXT","has_stderr":true},"japanese-lr-selection.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"japanese-rl-text.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"vertical-subst-font-vert-no-dflt.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]},"japanese-lr-text.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"japanese-rl-sel
ection.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"border-vertical-lr.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"fallback-orientation.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"dom":{"Geolocation":{"page-reload-cancel-permission-requests.html":{"expected":"FAIL TIMEOUT","actual":"TEXT","has_stderr":true}},"rtl-scroll-to-leftmost-and-resize.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"linkify-phone-numbers.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.11},&qu
ot;Window":{"window-properties-performance-resource-timing.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"window-properties-performance.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"window-open-self-disallow-close.html":{"expected":"PASS TIMEOUT CRASH","actual":"PASS","has_stderr":true}},"Range":{"compareBoundaryPoints-compareHow-exception.html":{"report":"REGRESSION","expected":"PASS","actual":"TEXT","has_stderr":true}},"MutationObserver":{"end-of-task-delivery.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"HTMLAnchorElement":{"anchor-nodownload-set.html":{"expected
":"FAIL","actual":"TEXT","has_stderr":true},"anchor-download.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"navigator-detached-no-crash.html":{"report":"REGRESSION","expected":"PASS","actual":"TEXT","has_stderr":true},"horizontal-scrollbar-in-rtl.html":{"expected":"FAIL","actual":"PASS","has_stderr":true},"HTMLMeterElement":{"meter-optimums.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"meter-element.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"meter-appearances-capacity.html":{"expected":"FAIL","actual":"TEXT","has_
stderr":true},"meter-styles.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"meter-element-repaint-on-update-value.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"meter-boundary-values.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"meter-appearances-rating-relevancy.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"gc-attribute-node.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true}},"backgrounds":{"background-opaque-images-over-color.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_dif
f_percent":0.01},"hidpi-bitmap-background-on-subpixel-position.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]}},"replaced":{"preferred-widths.html":{"expected":"PASS FAIL","actual":"PASS","has_stderr":true},"replaced-breaking.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true},"border-radius-clip.html":{"expected":"PASS FAIL","actual":"TEXT","has_stderr":true},"no-focus-ring-embed.html":{"report":"REGRESSION","expected":"PASS","actual":"TIMEOUT","has_stderr":true},"frame-removed-during-resize-smaller.html":{"expected":"PASS CRASH","actual":"PA
SS","has_stderr":true},"invalid-object-with-fallback.html":{"report":"REGRESSION","expected":"PASS","actual":"TEXT","has_stderr":true},"no-focus-ring-object.html":{"report":"REGRESSION","expected":"PASS","actual":"TIMEOUT","has_stderr":true}},"css3-text":{"css3-text-decoration":{"text-decoration-skip":{"text-decoration-skip-ink-inherit.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01}},"no-gap-between-two-rounded-textboxes.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":0.01}}},"block&q
uot;:{"float":{"016.html":{"expected":"FAIL","actual":"TEXT","has_stderr":true}},"positioning":{"fixed-container-with-relative-parent.html":{"expected":"PASS IMAGE","actual":"PASS","has_stderr":true,"reftest_type":["=="]}},"inside-inlines":{"opacity-on-inline.html":{"expected":"IMAGE","actual":"IMAGE","has_stderr":true,"reftest_type":["=="],"image_diff_percent":2.14}}},"history":{"page-cache-webdatabase-pending-transaction.html":{"expected":"PASS TIMEOUT","actual":"TIMEOUT","has_stderr":true},"history-back-while-pdf-in-pagecache.html":{"expected":"PASS IMAGE TIMEOUT CRASH","actual":"IMAGE","has_stderr&quo
t;:true,"reftest_type":["=="],"image_diff_percent":0.84}}}},"skipped":1372,"num_regressions":18,"other_crashes":{"DumpRenderTree-62625":{},"DumpRenderTree-63234":{},"DumpRenderTree-63209":{},"DumpRenderTree-63135":{}},"interrupted":false,"num_missing":0,"layout_tests_dir":"/Users/mlam/src/webkit/OpenSource/LayoutTests","version":4,"num_passes":12480,"pixel_tests_enabled":false,"date":"04:13PM on January 27, 2021","has_pretty_patch":true,"fixable":1556,"num_flaky":0,"uses_expectations_file":true});
\ No newline at end of file
Added: trunk/ManualTests/resources/test-results-page.html (0 => 272174)
--- trunk/ManualTests/resources/test-results-page.html (rev 0)
+++ trunk/ManualTests/resources/test-results-page.html 2021-02-01 21:40:55 UTC (rev 272174)
@@ -0,0 +1,2063 @@
+<!DOCTYPE html>
+
+<style>
+body {
+ margin: 0;
+ font-family: Helvetica, sans-serif;
+ font-size: 11pt;
+}
+
+body > * {
+ margin-left: 4px;
+ margin-top: 4px;
+}
+
+h1 {
+ font-size: 14pt;
+ margin-top: 1.5em;
+}
+
+p {
+ margin-bottom: 0.3em;
+}
+
+a.clickable {
+ color: blue;
+ cursor: pointer;
+ margin-left: 0.2em;
+}
+
+tr:not(.results-row) td {
+ white-space: nowrap;
+}
+
+tr:not(.results-row) td:first-of-type {
+ white-space: normal;
+}
+
+td:not(:first-of-type) {
+ text-transform: lowercase;
+}
+
+th, td {
+ padding: 1px 4px;
+ vertical-align: top;
+}
+
+td:nth-child(1) {
+ min-width: 35em;
+}
+
+th:empty, td:empty {
+ padding: 0;
+}
+
+th {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+}
+
+dt > sup {
+ vertical-align:text-top;
+ font-size:75%;
+}
+
+sup > a {
+ text-decoration: none;
+}
+
+.content-container {
+ min-height: 0;
+}
+
+.note {
+ color: gray;
+ font-size: smaller;
+}
+
+.results-row {
+ background-color: white;
+}
+
+.results-row iframe, .results-row img {
+ width: 800px;
+ height: 600px;
+}
+
+.results-row[data-expanded="false"] {
+ display: none;
+}
+
+#toolbar {
+ position: fixed;
+ top: 2px;
+ right: 2px;
+ text-align: right;
+}
+
+.floating-panel {
+ padding: 6px;
+ background-color: rgba(255, 255, 255, 0.9);
+ border: 1px solid silver;
+ border-radius: 4px;
+}
+
+.expand-button {
+ background-color: white;
+ width: 11px;
+ height: 12px;
+ border: 1px solid gray;
+ display: inline-block;
+ margin: 0 3px 0 0;
+ position: relative;
+ cursor: default;
+}
+
+.current {
+ color: red;
+}
+
+.current .expand-button {
+ border-color: red;
+}
+
+.expand-button-text {
+ position: absolute;
+ top: -0.3em;
+ left: 1px;
+}
+
+tbody .flag {
+ display: none;
+}
+
+tbody.flagged .flag {
+ display: inline;
+}
+
+.stopped-running-early-message {
+ border: 3px solid #d00;
+ font-weight: bold;
+ display: inline-block;
+ padding: 3px;
+}
+
+.result-container {
+ display: inline-block;
+ border: 1px solid gray;
+ margin: 4px;
+}
+
+.result-container iframe, .result-container img {
+ border: 0;
+ vertical-align: top;
+}
+
+.leaks > table {
+ margin: 4px;
+}
+.leaks > td {
+ padding-left: 20px;
+}
+
+#options {
+ background-color: white;
+}
+
+#options-menu {
+ border: 1px solid gray;
+ border-radius: 4px;
+ margin-top: 1px;
+ padding: 2px 4px;
+ box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.6);
+ transition: opacity .2s;
+ text-align: left;
+ position: absolute;
+ right: 4px;
+ background-color: white;
+}
+
+#options-menu label {
+ display: block;
+}
+
+.hidden-menu {
+ pointer-events: none;
+ opacity: 0;
+}
+
+.label {
+ padding-left: 3px;
+ font-weight: bold;
+ font-size: small;
+ background-color: silver;
+}
+
+.pixel-zoom-container {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 96%;
+ margin: 10px;
+ padding: 10px;
+ display: -webkit-box;
+ display: -moz-box;
+ pointer-events: none;
+ background-color: silver;
+ border-radius: 20px;
+ border: 1px solid gray;
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.75);
+}
+
+.pixel-zoom-container > * {
+ -webkit-box-flex: 1;
+ -moz-box-flex: 1;
+ border: 1px solid black;
+ margin: 4px;
+ overflow: hidden;
+ background-color: white;
+}
+
+.pixel-zoom-container .scaled-image-container {
+ position: relative;
+ overflow: hidden;
+ width: 100%;
+ height: 400px;
+}
+
+.scaled-image-container > img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ image-rendering: -webkit-optimize-contrast;
+}
+
+#flagged-test-container {
+ position: fixed;
+ bottom: 4px;
+ right: 4px;
+ width: 50%;
+ min-width: 400px;
+ background-color: rgba(255, 255, 255, 0.75);
+}
+
+#flagged-test-container > h2 {
+ margin: 0 0 4px 0;
+}
+
+#flagged-tests {
+ padding: 0 5px;
+ margin: 0;
+ height: 7em;
+ overflow-y: scroll;
+}
+</style>
+<style id="unexpected-style"></style>
+
+<script>
+
+class Utils
+{
+ static matchesSelector(node, selector)
+ {
+ if (node.matches)
+ return node.matches(selector);
+
+ if (node.webkitMatchesSelector)
+ return node.webkitMatchesSelector(selector);
+
+ if (node.mozMatchesSelector)
+ return node.mozMatchesSelector(selector);
+ }
+
+ static parentOfType(node, selector)
+ {
+ while (node = node.parentNode) {
+ if (Utils.matchesSelector(node, selector))
+ return node;
+ }
+ return null;
+ }
+
+ static stripExtension(testName)
+ {
+ // Temporary fix, also in Tools/Scripts/webkitpy/layout_tests/constrollers/test_result_writer.py, line 95.
+ // FIXME: Refactor to avoid confusing reference to both test and process names.
+ if (Utils.splitExtension(testName)[1].length > 5)
+ return testName;
+ return Utils.splitExtension(testName)[0];
+ }
+
+ static splitExtension(testName)
+ {
+ let index = testName.lastIndexOf('.');
+ if (index == -1) {
+ return [testName, ''];
+ }
+ return [testName.substring(0, index), testName.substring(index + 1)];
+ }
+
+ static forEach(nodeList, handler)
+ {
+ Array.prototype.forEach.call(nodeList, handler);
+ }
+
+ static toArray(nodeList)
+ {
+ return Array.prototype.slice.call(nodeList);
+ }
+
+ static trim(string)
+ {
+ return string.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
+ }
+
+ static async(func, args)
+ {
+ setTimeout(() => { func.apply(null, args); }, 50);
+ }
+
+ static appendHTML(node, html)
+ {
+ if (node.insertAdjacentHTML)
+ node.insertAdjacentHTML('beforeEnd', html);
+ else
+ node.innerHTML += html;
+ }};
+
+class TestResult
+{
+ constructor(info, name)
+ {
+ this.name = name;
+ this.info = info; // FIXME: make this private.
+ }
+
+ isFailureExpected()
+ {
+ let actual = this.info.actual;
+ let expected = this.info.expected || 'PASS';
+
+ if (actual != 'SKIP') {
+ let expectedArray = expected.split(' ');
+ let actualArray = actual.split(' ');
+ for (let actualValue of actualArray) {
+ if (expectedArray.indexOf(actualValue) == -1 && (expectedArray.indexOf('FAIL') == -1 || (actualValue != 'TEXT' && actualValue != 'IMAGE+TEXT' && actualValue != 'AUDIO')))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ isMissingAllResults()
+ {
+ return this.info.actual == 'MISSING';
+ }
+
+ hasMissingResult()
+ {
+ return this.info.actual.indexOf('MISSING') != -1;
+ }
+
+ isFlakey(pixelTestsEnabled)
+ {
+ let actualTokens = this.info.actual.split(' ');
+ let passedWithImageOnlyFailureInRetry = actualTokens[0] == 'TEXT' && actualTokens[1] == 'IMAGE';
+ if (actualTokens[1] && this.info.actual.indexOf('PASS') != -1 || (!pixelTestsEnabled && passedWithImageOnlyFailureInRetry))
+ return true;
+
+ return false;
+ }
+
+ isPass()
+ {
+ return this.info.actual == 'PASS';
+ }
+
+ isTextFailure()
+ {
+ return this.info.actual.indexOf('TEXT') != -1;
+ }
+
+ isImageFailure()
+ {
+ return this.info.actual.indexOf('IMAGE') != -1;
+ }
+
+ isAudioFailure()
+ {
+ return this.info.actual.indexOf('AUDIO') != -1;
+ }
+
+ hasLeak()
+ {
+ return this.info.actual.indexOf('LEAK') != -1;
+ }
+
+ isCrash()
+ {
+ return this.info.actual == 'CRASH';
+ }
+
+ isTimeout()
+ {
+ return this.info.actual == 'TIMEOUT';
+ }
+
+ isUnexpectedPass(pixelTestsEnabled)
+ {
+ if (this.info.actual == 'PASS' && this.info.expected != 'PASS') {
+ if (this.info.expected != 'IMAGE' || (pixelTestsEnabled || this.isRefTest()))
+ return true;
+ }
+
+ return false;
+ }
+
+ isRefTest()
+ {
+ return !!this.info.reftest_type;
+ }
+
+ isMismatchRefTest()
+ {
+ return this.isRefTest() && this.info.reftest_type.indexOf('!=') != -1;
+ }
+
+ isMatchRefTest()
+ {
+ return this.isRefTest() && this.info.reftest_type.indexOf('==') != -1;
+ }
+
+ isMissingText()
+ {
+ return this.info.is_missing_text;
+ }
+
+ isMissingImage()
+ {
+ return this.info.is_missing_image;
+ }
+
+ isMissingAudio()
+ {
+ return this.info.is_missing_audio;
+ }
+
+ hasStdErr()
+ {
+ return this.info.has_stderr;
+ }
+};
+
+class TestResults
+{
+ constructor(results)
+ {
+ this._results = results;
+
+ this.crashTests = [];
+ this.crashOther = [];
+ this.missingResults = [];
+ this.failingTests = [];
+ this.testsWithStderr = [];
+ this.timeoutTests = [];
+ this.unexpectedPassTests = [];
+ this.flakyPassTests = [];
+
+ this.hasHttpTests = false;
+ this.hasImageFailures = false;
+ this.hasTextFailures = false;
+
+ this._testsByName = new Map;
+
+ this._forEachTest(this._results.tests, '');
+ this._forOtherCrashes(this._results.other_crashes);
+ }
+
+ date()
+ {
+ return this._results.date;
+ }
+
+ layoutTestsDir()
+ {
+ return this._results.layout_tests_dir;
+ }
+
+ usesExpectationsFile()
+ {
+ return this._results.uses_expectations_file;
+ }
+
+ resultForTest(testName)
+ {
+ return this._resultsByTest[testName];
+ }
+
+ wasInterrupted()
+ {
+ return this._results.interrupted;
+ }
+
+ hasPrettyPatch()
+ {
+ return this._results.has_pretty_patch;
+ }
+
+ hasWDiff()
+ {
+ return this._results.has_wdiff;
+ }
+
+ testWithName(testName)
+ {
+ return this._testsByName.get(testName);
+ }
+
+ _processResultForTest(testResult)
+ {
+ this._testsByName.set(testResult.name, testResult);
+
+ let test = testResult.name;
+ if (testResult.hasStdErr())
+ this.testsWithStderr.push(testResult);
+
+ this.hasHttpTests |= test.indexOf('http/') == 0;
+
+ if (this.usesExpectationsFile())
+ testResult.isExpected = testResult.isFailureExpected();
+
+ if (testResult.isTextFailure())
+ this.hasTextFailures = true;
+
+ if (testResult.isImageFailure())
+ this.hasImageFailures = true;
+
+ if (testResult.isMissingAllResults()) {
+ // FIXME: make sure that new-run-webkit-tests spits out an -actual.txt file for tests with MISSING results.
+ this.missingResults.push(testResult);
+ return;
+ }
+
+ if (testResult.isFlakey(this._results.pixel_tests_enabled)) {
+ this.flakyPassTests.push(testResult);
+ return;
+ }
+
+ if (testResult.isPass()) {
+ if (testResult.isUnexpectedPass(this._results.pixel_tests_enabled))
+ this.unexpectedPassTests.push(testResult);
+ return;
+ }
+
+ if (testResult.isCrash()) {
+ this.crashTests.push(testResult);
+ return;
+ }
+
+ if (testResult.isTimeout()) {
+ this.timeoutTests.push(testResult);
+ return;
+ }
+
+ this.failingTests.push(testResult);
+ }
+
+ _forEachTest(tree, prefix)
+ {
+ for (let key in tree) {
+ let newPrefix = prefix ? (prefix + '/' + key) : key;
+ if ('actual' in tree[key]) {
+ let testObject = new TestResult(tree[key], newPrefix);
+ this._processResultForTest(testObject);
+ } else
+ this._forEachTest(tree[key], newPrefix);
+ }
+ }
+
+ _forOtherCrashes(tree)
+ {
+ for (let key in tree) {
+ let testObject = new TestResult(tree[key], key);
+ this.crashOther.push(testObject);
+ }
+ }
+
+ static sortByName(tests)
+ {
+ tests.sort(function (a, b) { return a.name.localeCompare(b.name) });
+ }
+
+ static hasUnexpectedResult(tests)
+ {
+ return tests.some(function (test) { return !test.isExpected; });
+ }
+};
+
+class TestResultsController
+{
+ constructor(containerElement, testResults)
+ {
+ this.containerElement = containerElement;
+ this.testResults = testResults;
+
+ this.shouldToggleImages = true;
+ this._togglingImageInterval = null;
+
+ this._updatePageTitle();
+
+ this.buildResultsTables();
+ this.hideNonApplicableUI();
+ this.setupSorting();
+ this.setupOptions();
+ }
+
+ buildResultsTables()
+ {
+ if (this.testResults.wasInterrupted()) {
+ let interruptionMessage = document.createElement('p');
+ interruptionMessage.textContent = 'Testing exited early';
+ interruptionMessage.classList.add('stopped-running-early-message');
+ this.containerElement.appendChild(interruptionMessage);
+ }
+
+ if (this.testResults.crashTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.crashTests, 'crash-tests-table'));
+
+ if (this.testResults.crashOther.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.crashOther, 'other-crash-tests-table'));
+
+ if (this.testResults.failingTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.failingTests, 'results-table'));
+
+ if (this.testResults.missingResults.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.missingResults, 'missing-table'));
+
+ if (this.testResults.timeoutTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.timeoutTests, 'timeout-tests-table'));
+
+ if (this.testResults.testsWithStderr.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.testsWithStderr, 'stderr-table'));
+
+ if (this.testResults.flakyPassTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.flakyPassTests, 'flaky-tests-table'));
+
+ if (this.testResults.usesExpectationsFile() && this.testResults.unexpectedPassTests.length)
+ this.containerElement.appendChild(this.buildOneSection(this.testResults.unexpectedPassTests, 'passes-table'));
+
+ if (this.testResults.hasHttpTests) {
+ let httpdAccessLogLink = document.createElement('p');
+ httpdAccessLogLink.innerHTML = 'httpd access log: <a href=""
+
+ let httpdErrorLogLink = document.createElement('p');
+ httpdErrorLogLink.innerHTML = 'httpd error log: <a href=""
+
+ this.containerElement.appendChild(httpdAccessLogLink);
+ this.containerElement.appendChild(httpdErrorLogLink);
+ }
+
+ this.updateTestlistCounts();
+ }
+
+ static sectionBuilderClassForTableID(tableID)
+ {
+ const idToBuilderClassMap = {
+ 'crash-tests-table' : CrashingTestsSectionBuilder,
+ 'other-crash-tests-table' : OtherCrashesSectionBuilder,
+ 'results-table' : FailingTestsSectionBuilder,
+ 'missing-table' : TestsWithMissingResultsSectionBuilder,
+ 'timeout-tests-table' : TimedOutTestsSectionBuilder,
+ 'stderr-table' : TestsWithStdErrSectionBuilder,
+ 'flaky-tests-table' : FlakyPassTestsSectionBuilder,
+ 'passes-table' : UnexpectedPassTestsSectionBuilder,
+ };
+ return idToBuilderClassMap[tableID];
+ }
+
+ setupSorting()
+ {
+ let resultsTable = document.getElementById('results-table');
+ if (!resultsTable)
+ return;
+
+ // FIXME: Make all the tables sortable. Maybe SectionBuilder should put a TableSorter on each table.
+ resultsTable.addEventListener('click', TableSorter.handleClick, false);
+ TableSorter.sortColumn(0);
+ }
+
+ hideNonApplicableUI()
+ {
+ // FIXME: do this all through body classnames.
+ if (!this.testResults.hasTextFailures) {
+ let textResultsHeader = document.getElementById('text-results-header');
+ if (textResultsHeader)
+ textResultsHeader.textContent = '';
+ }
+
+ if (!this.testResults.hasImageFailures) {
+ let imageResultsHeader = document.getElementById('image-results-header');
+ if (imageResultsHeader)
+ imageResultsHeader.textContent = '';
+
+ Utils.parentOfType(document.getElementById('toggle-images'), 'label').style.display = 'none';
+ }
+ }
+
+ setupOptions()
+ {
+ // FIXME: do this all through body classnames.
+ if (!this.testResults.usesExpectationsFile())
+ Utils.parentOfType(document.getElementById('unexpected-results'), 'label').style.display = 'none';
+ }
+
+ buildOneSection(tests, tableID)
+ {
+ TestResults.sortByName(tests);
+
+ let sectionBuilderClass = TestResultsController.sectionBuilderClassForTableID(tableID);
+ let sectionBuilder = new sectionBuilderClass(tests, tableID, this);
+ return sectionBuilder.build();
+ }
+
+ updateTestlistCounts()
+ {
+ // FIXME: do this through the data model, not through the DOM.
+ let _onlyShowUnexpectedFailures_ = this.onlyShowUnexpectedFailures();
+ Utils.forEach(document.querySelectorAll('.test-list-count'), count => {
+ let container = Utils.parentOfType(count, 'section');
+ let testContainers;
+ if (onlyShowUnexpectedFailures)
+ testContainers = container.querySelectorAll('tbody:not(.expected)');
+ else
+ testContainers = container.querySelectorAll('tbody');
+
+ count.textContent = testContainers.length;
+ })
+ }
+
+ flagAll(headerLink)
+ {
+ let tests = this.visibleTests(Utils.parentOfType(headerLink, 'section'));
+ Utils.forEach(tests, tests => {
+ let shouldFlag = true;
+ testNavigator.flagTest(tests, shouldFlag);
+ })
+ }
+
+ unflag(flag)
+ {
+ const shouldFlag = false;
+ testNavigator.flagTest(Utils.parentOfType(flag, 'tbody'), shouldFlag);
+ }
+
+ visibleTests(opt_container)
+ {
+ let container = opt_container || document;
+ if (this.onlyShowUnexpectedFailures())
+ return container.querySelectorAll('tbody:not(.expected)');
+ else
+ return container.querySelectorAll('tbody');
+ }
+
+ // FIXME: this is confusing. Flip the sense around.
+ onlyShowUnexpectedFailures()
+ {
+ return document.getElementById('unexpected-results').checked;
+ }
+
+ static _testListHeader(title)
+ {
+ let header = document.createElement('h1');
+ header.innerHTML = title + ' (<span class=test-list-count></span>): <a href="" class=flag-all _onclick_="controller.flagAll(this)">flag all</a>';
+ return header;
+ }
+
+ testToURL(testResult, layoutTestsPath)
+ {
+ const mappings = {
+ "http/tests/ssl/": "https://127.0.0.1:8443/ssl/",
+ "http/tests/": "http://127.0.0.1:8000/",
+ "http/wpt/": "http://localhost:8800/WebKit/",
+ "imported/w3c/web-platform-tests/": "http://localhost:8800/"
+ };
+
+ for (let key in mappings) {
+ if (testResult.name.startsWith(key))
+ return mappings[key] + testResult.name.substring(key.length);
+
+ }
+ return "file://" + layoutTestsPath + "/" + testResult.name;
+ }
+
+ layoutTestURL(testResult)
+ {
+ if (this.shouldUseTracLinks())
+ return this.layoutTestsBasePath() + testResult.name;
+
+ return this.testToURL(testResult, this.layoutTestsBasePath());
+ }
+
+ layoutTestsBasePath()
+ {
+ let basePath;
+ if (this.shouldUseTracLinks()) {
+ let revision = this.testResults.revision;
+ basePath = 'http://trac.webkit.org';
+ basePath += revision ? ('/export/' + revision) : '/browser';
+ basePath += '/trunk/LayoutTests/';
+ } else
+ basePath = this.testResults.layoutTestsDir() + '/';
+
+ return basePath;
+ }
+
+ convertToLayoutTestBaseRelativeURL(fullURL)
+ {
+ if (fullURL.startsWith('file://')) {
+ let urlPrefix = 'file://' + this.layoutTestsBasePath();
+ if (fullURL.startsWith(urlPrefix))
+ return fullURL.substring(urlPrefix.length);
+ }
+
+ return fullURL;
+ }
+
+ shouldUseTracLinks()
+ {
+ return !this.testResults.layoutTestsDir() || !location.toString().indexOf('file://') == 0;
+ }
+
+ checkServerIsRunning(event)
+ {
+ if (this.shouldUseTracLinks())
+ return;
+
+ let url = ""
+ if (url.startsWith("file://"))
+ return;
+
+ event.preventDefault();
+ fetch(url, { mode: "no-cors" }).then(() => {
+ window.location = url;
+ }, () => {
+ alert("HTTP server does not seem to be running, please use the run-webkit-httpd script");
+ });
+ }
+
+ testLink(testResult)
+ {
+ return '<a class=test-link _onclick_="controller.checkServerIsRunning(event)" href="" + this.layoutTestURL(testResult) + '">' + testResult.name + '</a><span class=flag _onclick_="controller.unflag(this)"> \u2691</span>';
+ }
+
+ expandButtonSpan()
+ {
+ return '<span class=expand-button _onclick_="controller.toggleExpectations(this)"><span class=expand-button-text>+</span></span>';
+ }
+
+ static resultLink(testPrefix, suffix, contents)
+ {
+ return '<a class=result-link href="" + testPrefix + suffix + '" data-prefix="' + testPrefix + '">' + contents + '</a> ';
+ }
+
+ textResultLinks(prefix)
+ {
+ let html = TestResultsController.resultLink(prefix, '-expected.txt', 'expected') +
+ TestResultsController.resultLink(prefix, '-actual.txt', 'actual') +
+ TestResultsController.resultLink(prefix, '-diff.txt', 'diff');
+
+ if (this.testResults.hasPrettyPatch())
+ html += TestResultsController.resultLink(prefix, '-pretty-diff.html', 'pretty diff');
+
+ if (this.testResults.hasWDiff())
+ html += TestResultsController.resultLink(prefix, '-wdiff.html', 'wdiff');
+
+ return html;
+ }
+
+ flakinessDashboardURLForTests(testObjects)
+ {
+ // FIXME: just map and join here.
+ let testList = '';
+ for (let i = 0; i < testObjects.length; ++i) {
+ testList += testObjects[i].name;
+
+ if (i != testObjects.length - 1)
+ testList += ',';
+ }
+
+ return 'https://results.webkit.org/?suite=layout-tests&test=' + encodeURIComponent(testList);
+ }
+
+ _updatePageTitle()
+ {
+ let dateString = this.testResults.date();
+ let title = document.createElement('title');
+ title.textContent = 'Layout Test Results from ' + dateString;
+ document.head.appendChild(title);
+ }
+
+ // Options handling. FIXME: move to a separate class?
+ updateAllOptions()
+ {
+ Utils.forEach(document.querySelectorAll('#options-menu input'), input => { input.onchange() });
+ }
+
+ toggleOptionsMenu()
+ {
+ let menu = document.getElementById('options-menu');
+ menu.className = (menu.className == 'hidden-menu') ? '' : 'hidden-menu';
+ }
+
+ handleToggleUseNewlines()
+ {
+ OptionWriter.save();
+ testNavigator.updateFlaggedTests();
+ }
+
+ handleUnexpectedResultsChange()
+ {
+ OptionWriter.save();
+ this._updateExpectedFailures();
+ }
+
+ expandAllExpectations()
+ {
+ let expandLinks = this._visibleExpandLinks();
+ for (let link of expandLinks)
+ Utils.async(link => { controller.expandExpectations(link) }, [ link ]);
+ }
+
+ collapseAllExpectations()
+ {
+ let expandLinks = this._visibleExpandLinks();
+ for (let link of expandLinks)
+ Utils.async(link => { controller.collapseExpectations(link) }, [ link ]);
+ }
+
+ expandExpectations(expandLink)
+ {
+ let row = Utils.parentOfType(expandLink, 'tr');
+ let parentTbody = row.parentNode;
+ let existingResultsRow = parentTbody.querySelector('.results-row');
+
+ const enDash = '\u2013';
+ expandLink.textContent = enDash;
+ if (existingResultsRow) {
+ this._updateExpandedState(existingResultsRow, true);
+ return;
+ }
+
+ let testName = row.getAttribute('data-test-name');
+ let testResult = this.testResults.testWithName(testName);
+
+ let newRow = TestResultsController._buildExpandedRowForTest(testResult, row);
+ parentTbody.appendChild(newRow);
+
+ this._updateExpandedState(newRow, true);
+
+ this._updateImageTogglingTimer();
+ }
+
+ collapseExpectations(expandLink)
+ {
+ expandLink.textContent = '+';
+ let existingResultsRow = Utils.parentOfType(expandLink, 'tbody').querySelector('.results-row');
+ if (existingResultsRow)
+ this._updateExpandedState(existingResultsRow, false);
+ }
+
+ toggleExpectations(element)
+ {
+ let expandLink = element;
+ if (expandLink.className != 'expand-button-text')
+ expandLink = expandLink.querySelector('.expand-button-text');
+
+ if (expandLink.textContent == '+')
+ this.expandExpectations(expandLink);
+ else
+ this.collapseExpectations(expandLink);
+ }
+
+ _updateExpandedState(row, isExpanded)
+ {
+ row.setAttribute('data-expanded', isExpanded);
+ this._updateImageTogglingTimer();
+ }
+
+ handleToggleImagesChange()
+ {
+ OptionWriter.save();
+ this._updateTogglingImages();
+ }
+
+ _visibleExpandLinks()
+ {
+ if (this.onlyShowUnexpectedFailures())
+ return document.querySelectorAll('tbody:not(.expected) .expand-button-text');
+ else
+ return document.querySelectorAll('.expand-button-text');
+ }
+
+ static _togglingImage(prefix)
+ {
+ return '<div class=result-container><div class="label imageText"></div><img class=animatedImage data-prefix="' + prefix + '"></img></div>';
+ }
+
+ _updateTogglingImages()
+ {
+ this.shouldToggleImages = document.getElementById('toggle-images').checked;
+
+ // FIXME: this is all pretty confusing. Simplify.
+ if (this.shouldToggleImages) {
+ Utils.forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) a[href$=".png"]'), TestResultsController._convertToTogglingHandler(function(prefix) {
+ return TestResultsController.resultLink(prefix, '-diffs.html', 'images');
+ }));
+ Utils.forEach(document.querySelectorAll('table:not(#missing-table) tbody:not([mismatchreftest]) img[src$=".png"]'), TestResultsController._convertToTogglingHandler(TestResultsController._togglingImage));
+ } else {
+ Utils.forEach(document.querySelectorAll('a[href$="-diffs.html"]'), element => {
+ TestResultsController._convertToNonTogglingHandler(element);
+ });
+ Utils.forEach(document.querySelectorAll('.animatedImage'), TestResultsController._convertToNonTogglingHandler(function (absolutePrefix, suffix) {
+ return TestResultsController._resultIframe(absolutePrefix + suffix);
+ }));
+ }
+
+ this._updateImageTogglingTimer();
+ }
+
+ _updateExpectedFailures()
+ {
+ // Gross to do this by setting stylesheet text. Use a body class!
+ document.getElementById('unexpected-style').textContent = this.onlyShowUnexpectedFailures() ? '.expected { display: none; }' : '';
+
+ this.updateTestlistCounts();
+ testNavigator.onlyShowUnexpectedFailuresChanged();
+ }
+
+ static _buildExpandedRowForTest(testResult, row)
+ {
+ let newRow = document.createElement('tr');
+ newRow.className = 'results-row';
+ let newCell = document.createElement('td');
+ newCell.colSpan = row.querySelectorAll('td').length;
+
+ // FIXME: migrate more of code to using testResult for building the expanded content.
+ let resultLinks = row.querySelectorAll('.result-link');
+ let hasTogglingImages = false;
+ for (let link of resultLinks) {
+ let result;
+ if (link.textContent == 'images') {
+ hasTogglingImages = true;
+ result = TestResultsController._togglingImage(link.getAttribute('data-prefix'));
+ } else
+ result = TestResultsController._resultIframe(link.href);
+
+ Utils.appendHTML(newCell, result);
+ }
+
+ if (testResult.hasLeak())
+ newCell.appendChild(TestResultsController._makeLeaksCell(testResult));
+
+ newRow.appendChild(newCell);
+
+ return newRow;
+ }
+
+ static _makeLeaksCell(testResult)
+ {
+ let container = document.createElement('div');
+ container.className = 'result-container leaks';
+
+ let label = document.createElement('div');
+ label.className = 'label';
+ label.textContent = "Leaks";
+ container.appendChild(label);
+
+ let leaksTable = document.createElement('table');
+
+ for (let leak of testResult.info.leaks) {
+ let leakRow = document.createElement('tr');
+
+ for (let leakedObjectType in leak) {
+ let th = document.createElement('th');
+ th.textContent = leakedObjectType;
+ let td = document.createElement('td');
+
+ let url = "" // FIXME: when we track leaks other than documents, this might not be a URL.
+ td.textContent = controller.convertToLayoutTestBaseRelativeURL(url)
+
+ leakRow.appendChild(th);
+ leakRow.appendChild(td);
+ }
+
+ leaksTable.appendChild(leakRow);
+ }
+
+ container.appendChild(leaksTable);
+ return container;
+ }
+
+ static _resultIframe(src)
+ {
+ // FIXME: use audio tags for AUDIO tests?
+ let layoutTestsIndex = src.indexOf('LayoutTests');
+ let name;
+ if (layoutTestsIndex != -1) {
+ let hasTrac = src.indexOf('trac.webkit.org') != -1;
+ let prefix = hasTrac ? 'trac.webkit.org/.../' : '';
+ name = prefix + src.substring(layoutTestsIndex + 'LayoutTests/'.length);
+ } else {
+ let lastDashIndex = src.lastIndexOf('-pretty');
+ if (lastDashIndex == -1)
+ lastDashIndex = src.lastIndexOf('-');
+ name = src.substring(lastDashIndex + 1);
+ }
+
+ let tagName = (src.lastIndexOf('.png') == -1) ? 'iframe' : 'img';
+
+ if (tagName != 'img')
+ src += '?format=txt';
+ return '<div class=result-container><div class=label>' + name + '</div><' + tagName + ' src="" + src + '"></' + tagName + '></div>';
+ }
+
+
+ static _toggleImages()
+ {
+ let images = document.querySelectorAll('.animatedImage');
+ let imageTexts = document.querySelectorAll('.imageText');
+ for (let i = 0, len = images.length; i < len; i++) {
+ let image = images[i];
+ let text = imageTexts[i];
+ if (text.textContent == 'Expected Image') {
+ text.textContent = 'Actual Image';
+ image.src = "" + '-actual.png';
+ } else {
+ text.textContent = 'Expected Image';
+ image.src = "" + '-expected.png';
+ }
+ }
+ }
+
+ _updateImageTogglingTimer()
+ {
+ let hasVisibleAnimatedImage = document.querySelector('.results-row[data-expanded="true"] .animatedImage');
+ if (!hasVisibleAnimatedImage) {
+ clearInterval(this._togglingImageInterval);
+ this._togglingImageInterval = null;
+ return;
+ }
+
+ if (!this._togglingImageInterval) {
+ TestResultsController._toggleImages();
+ this._togglingImageInterval = setInterval(TestResultsController._toggleImages, 2000);
+ }
+ }
+
+ static _getResultContainer(node)
+ {
+ return (node.tagName == 'IMG') ? Utils.parentOfType(node, '.result-container') : node;
+ }
+
+ static _convertToTogglingHandler(togglingImageFunction)
+ {
+ return function(node) {
+ let url = "" == 'IMG') ? node.src : node.href;
+ if (url.match('-expected.png$'))
+ TestResultsController._getResultContainer(node).remove();
+ else if (url.match('-actual.png$')) {
+ let name = Utils.parentOfType(node, 'tbody').querySelector('.test-link').textContent;
+ TestResultsController._getResultContainer(node).outerHTML = togglingImageFunction(Utils.stripExtension(name));
+ }
+ }
+ }
+
+ static _convertToNonTogglingHandler(resultFunction)
+ {
+ return function(node) {
+ let prefix = node.getAttribute('data-prefix');
+ TestResultsController._getResultContainer(node).outerHTML = resultFunction(prefix, '-expected.png', 'expected') + resultFunction(prefix, '-actual.png', 'actual');
+ }
+ }
+};
+
+class SectionBuilder {
+
+ constructor(tests, tableID, resultsController)
+ {
+ this._tests = tests;
+ this._table = null;
+ this._resultsController = resultsController;
+ this._tableID = tableID;
+ }
+
+ build()
+ {
+ TestResults.sortByName(this._tests);
+
+ let section = document.createElement('section');
+ section.appendChild(TestResultsController._testListHeader(this.sectionTitle()));
+ if (this.hideWhenShowingUnexpectedResultsOnly())
+ section.classList.add('expected');
+
+ this._table = document.createElement('table');
+ this._table.id = this.tableID();
+ this.addTableHeader();
+
+ let visibleResultsCount = 0;
+ for (let testResult of this._tests) {
+ let tbody = this.createTableRow(testResult);
+ this._table.appendChild(tbody);
+
+ if (!this._resultsController.onlyShowUnexpectedFailures() || testResult.isExpected)
+ ++visibleResultsCount;
+ }
+
+ section.querySelector('.test-list-count').textContent = visibleResultsCount;
+ section.appendChild(this._table);
+ return section;
+ }
+
+ createTableRow(testResult)
+ {
+ let tbody = document.createElement('tbody');
+ if (testResult.isExpected)
+ tbody.classList.add('expected');
+
+ let row = document.createElement('tr');
+ row.setAttribute('data-test-name', testResult.name);
+ tbody.appendChild(row);
+
+ let testNameCell = document.createElement('td');
+ this.fillTestCell(testResult, testNameCell);
+ row.appendChild(testNameCell);
+
+ let resultCell = document.createElement('td');
+ this.fillTestResultCell(testResult, resultCell);
+ row.appendChild(resultCell);
+
+ let historyCell = this.createHistoryCell(testResult);
+ if (historyCell)
+ row.appendChild(historyCell);
+
+ return tbody;
+ }
+
+ hideWhenShowingUnexpectedResultsOnly()
+ {
+ return !TestResults.hasUnexpectedResult(this._tests);
+ }
+
+ addTableHeader()
+ {
+ }
+
+ fillTestCell(testResult, cell)
+ {
+ let testLink = this.linkifyTestName() ? this._resultsController.testLink(testResult) : testResult.name;
+ if (this.rowsAreExpandable()) {
+ cell.innerHTML = this._resultsController.expandButtonSpan() + testLink;
+ return;
+ }
+
+ cell.innerHTML = testLink;
+ }
+
+ fillTestResultCell(testResult, cell)
+ {
+ }
+
+ createHistoryCell(testResult)
+ {
+ let historyCell = document.createElement('td');
+ historyCell.innerHTML = '<a href="" + this._resultsController.flakinessDashboardURLForTests([testResult]) + '">history</a>'
+ return historyCell;
+ }
+
+ tableID()
+ {
+ return this._tableID;
+ }
+
+ rowsAreExpandable()
+ {
+ return true;
+ }
+
+ linkifyTestName()
+ {
+ return true;
+ }
+
+ sectionTitle() { return ''; }
+};
+
+class FailuresSectionBuilder extends SectionBuilder {
+
+ addTableHeader()
+ {
+ let header = document.createElement('thead');
+ let html = '<th>test</th><th id="text-results-header">results</th><th id="image-results-header">image results</th>';
+
+ if (this._resultsController.testResults.usesExpectationsFile())
+ html += '<th>actual failure</th><th>expected failure</th>';
+
+ html += '<th><a href="" + this._resultsController.flakinessDashboardURLForTests(this._tests) + '">history</a></th>';
+
+ if (this.tableID() == 'flaky-tests-table') // FIXME: use the classes, Luke!
+ html += '<th>failures</th>';
+
+ header.innerHTML = html;
+ this._table.appendChild(header);
+ }
+
+ createTableRow(testResult)
+ {
+ let tbody = document.createElement('tbody');
+ if (testResult.isExpected)
+ tbody.classList.add('expected');
+
+ if (testResult.isMismatchRefTest())
+ tbody.setAttribute('mismatchreftest', 'true');
+
+ let row = document.createElement('tr');
+ row.setAttribute('data-test-name', testResult.name);
+ tbody.appendChild(row);
+
+ let testNameCell = document.createElement('td');
+ this.fillTestCell(testResult, testNameCell);
+ row.appendChild(testNameCell);
+
+ let resultCell = document.createElement('td');
+ this.fillTestResultCell(testResult, resultCell);
+ row.appendChild(resultCell);
+
+ if (testResult.isTextFailure())
+ this.appendTextFailureLinks(testResult, resultCell);
+
+ if (testResult.isAudioFailure())
+ this.appendAudioFailureLinks(testResult, resultCell);
+
+ if (testResult.hasMissingResult())
+ this.appendActualOnlyLinks(testResult, resultCell);
+
+ let actualTokens = testResult.info.actual.split(/\s+/);
+
+ let testPrefix = Utils.stripExtension(testResult.name);
+ let imageResults = this.imageResultLinks(testResult, testPrefix, actualTokens[0]);
+ if (!imageResults && actualTokens.length > 1)
+ imageResults = this.imageResultLinks(testResult, 'retries/' + testPrefix, actualTokens[1]);
+
+ let imageResultsCell = document.createElement('td');
+ imageResultsCell.innerHTML = imageResults;
+ row.appendChild(imageResultsCell);
+
+ if (this._resultsController.testResults.usesExpectationsFile() || actualTokens.length) {
+ let actualCell = document.createElement('td');
+ actualCell.textContent = testResult.info.actual;
+ row.appendChild(actualCell);
+ }
+
+ if (this._resultsController.testResults.usesExpectationsFile()) {
+ let expectedCell = document.createElement('td');
+ expectedCell.textContent = testResult.hasMissingResult() ? '' : testResult.info.expected;
+ row.appendChild(expectedCell);
+ }
+
+ let historyCell = this.createHistoryCell(testResult);
+ if (historyCell)
+ row.appendChild(historyCell);
+
+ return tbody;
+ }
+
+ appendTextFailureLinks(testResult, cell)
+ {
+ cell.innerHTML += this._resultsController.textResultLinks(Utils.stripExtension(testResult.name));
+ }
+
+ appendAudioFailureLinks(testResult, cell)
+ {
+ let prefix = Utils.stripExtension(testResult.name);
+ cell.innerHTML += TestResultsController.resultLink(prefix, '-expected.wav', 'expected audio')
+ + TestResultsController.resultLink(prefix, '-actual.wav', 'actual audio')
+ + TestResultsController.resultLink(prefix, '-diff.txt', 'textual diff');
+ }
+
+ appendActualOnlyLinks(testResult, cell)
+ {
+ let prefix = Utils.stripExtension(testResult.name);
+ if (testResult.isMissingAudio())
+ cell.innerHTML += TestResultsController.resultLink(prefix, '-actual.wav', 'audio result');
+
+ if (testResult.isMissingText())
+ cell.innerHTML += TestResultsController.resultLink(prefix, '-actual.txt', 'result');
+ }
+
+ imageResultLinks(testResult, testPrefix, resultToken)
+ {
+ let result = '';
+ if (resultToken.indexOf('IMAGE') != -1) {
+ let testExtension = Utils.splitExtension(testResult.name)[1];
+
+ if (testResult.isMismatchRefTest()) {
+ result += TestResultsController.resultLink(this._resultsController.layoutTestsBasePath() + testPrefix, '-expected-mismatch.' + testExtension, 'ref mismatch');
+ result += TestResultsController.resultLink(testPrefix, '-actual.png', 'actual');
+ } else {
+ if (testResult.isMatchRefTest())
+ result += TestResultsController.resultLink(this._resultsController.layoutTestsBasePath() + testPrefix, '-expected.' + testExtension, 'reference');
+
+ if (this._resultsController.shouldToggleImages)
+ result += TestResultsController.resultLink(testPrefix, '-diffs.html', 'images');
+ else {
+ result += TestResultsController.resultLink(testPrefix, '-expected.png', 'expected');
+ result += TestResultsController.resultLink(testPrefix, '-actual.png', 'actual');
+ }
+
+ let diff = testResult.info.image_diff_percent;
+ result += TestResultsController.resultLink(testPrefix, '-diff.png', 'diff (' + diff + '%)');
+ }
+ }
+
+ if (testResult.hasMissingResult() && testResult.isMissingImage())
+ result += TestResultsController.resultLink(testPrefix, '-actual.png', 'png result');
+
+ return result;
+ }
+};
+
+class FailingTestsSectionBuilder extends FailuresSectionBuilder {
+ sectionTitle() { return 'Tests that failed text/pixel/audio diff'; }
+};
+
+class TestsWithMissingResultsSectionBuilder extends FailuresSectionBuilder {
+ sectionTitle() { return 'Tests that had no expected results (probably new)'; }
+
+ rowsAreExpandable()
+ {
+ return false;
+ }
+};
+
+class FlakyPassTestsSectionBuilder extends FailuresSectionBuilder {
+ sectionTitle() { return 'Flaky tests (failed the first run and passed on retry)'; }
+};
+
+class UnexpectedPassTestsSectionBuilder extends SectionBuilder {
+ sectionTitle() { return 'Tests expected to fail but passed'; }
+
+ addTableHeader()
+ {
+ let header = document.createElement('thead');
+ header.innerHTML = '<th>test</th><th>expected failure</th><th>history</th>';
+ this._table.appendChild(header);
+ }
+
+ fillTestResultCell(testResult, cell)
+ {
+ cell.innerHTML = testResult.info.expected;
+ }
+
+ rowsAreExpandable()
+ {
+ return false;
+ }
+};
+
+class TestsWithStdErrSectionBuilder extends SectionBuilder {
+ sectionTitle() { return 'Tests that had stderr output'; }
+ hideWhenShowingUnexpectedResultsOnly() { return false; }
+
+ fillTestResultCell(testResult, cell)
+ {
+ cell.innerHTML = TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-stderr.txt', 'stderr');
+ }
+};
+
+class TimedOutTestsSectionBuilder extends SectionBuilder {
+ sectionTitle() { return 'Tests that timed out'; }
+
+ fillTestResultCell(testResult, cell)
+ {
+ // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests.
+ cell.innerHTML = this._resultsController.textResultLinks(Utils.stripExtension(testResult.name));
+ }
+};
+
+class CrashingTestsSectionBuilder extends SectionBuilder {
+ sectionTitle() { return 'Tests that crashed'; }
+
+ fillTestResultCell(testResult, cell)
+ {
+ cell.innerHTML = TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-crash-log.txt', 'crash log')
+ + TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-sample.txt', 'sample');
+ }
+};
+
+class OtherCrashesSectionBuilder extends SectionBuilder {
+ sectionTitle() { return 'Other crashes'; }
+ fillTestResultCell(testResult, cell)
+ {
+ cell.innerHTML = TestResultsController.resultLink(Utils.stripExtension(testResult.name), '-crash-log.txt', 'crash log');
+ }
+
+ createHistoryCell(testResult)
+ {
+ return null;
+ }
+
+ linkifyTestName()
+ {
+ return false;
+ }
+};
+
+class PixelZoomer {
+ constructor()
+ {
+ this.showOnDelay = true;
+ this._zoomFactor = 6;
+
+ this._resultWidth = 800;
+ this._resultHeight = 600;
+
+ this._percentX = 0;
+ this._percentY = 0;
+
+ document.addEventListener('mousemove', this, false);
+ document.addEventListener('mouseout', this, false);
+ }
+
+ _zoomedResultWidth()
+ {
+ return this._resultWidth * this._zoomFactor;
+ }
+
+ _zoomedResultHeight()
+ {
+ return this._resultHeight * this._zoomFactor;
+ }
+
+ _zoomImageContainer(url)
+ {
+ let container = document.createElement('div');
+ container.className = 'zoom-image-container';
+
+ let title = url.match(/\-([^\-]*)\.png/)[1];
+
+ let label = document.createElement('div');
+ label.className = 'label';
+ label.appendChild(document.createTextNode(title));
+ container.appendChild(label);
+
+ let imageContainer = document.createElement('div');
+ imageContainer.className = 'scaled-image-container';
+
+ let image = new Image();
+ image.src = ""
+ image.style.width = this._zoomedResultWidth() + 'px';
+ image.style.height = this._zoomedResultHeight() + 'px';
+ image.style.border = '1px solid black';
+ imageContainer.appendChild(image);
+ container.appendChild(imageContainer);
+
+ return container;
+ }
+
+ _createContainer(e)
+ {
+ let tbody = Utils.parentOfType(e.target, 'tbody');
+ let row = tbody.querySelector('tr');
+ let imageDiffLinks = row.querySelectorAll('a[href$=".png"]');
+
+ let container = document.createElement('div');
+ container.className = 'pixel-zoom-container';
+
+ let html = '';
+
+ let togglingImageLink = row.querySelector('a[href$="-diffs.html"]');
+ if (togglingImageLink) {
+ let prefix = togglingImageLink.getAttribute('data-prefix');
+ container.appendChild(this._zoomImageContainer(prefix + '-expected.png'));
+ container.appendChild(this._zoomImageContainer(prefix + '-actual.png'));
+ }
+
+ for (let link of imageDiffLinks)
+ container.appendChild(this._zoomImageContainer(link.href));
+
+ document.body.appendChild(container);
+ this._drawAll();
+ }
+
+ _draw(imageContainer)
+ {
+ let image = imageContainer.querySelector('img');
+ let containerBounds = imageContainer.getBoundingClientRect();
+ image.style.left = (containerBounds.width / 2 - this._percentX * this._zoomedResultWidth()) + 'px';
+ image.style.top = (containerBounds.height / 2 - this._percentY * this._zoomedResultHeight()) + 'px';
+ }
+
+ _drawAll()
+ {
+ Utils.forEach(document.querySelectorAll('.pixel-zoom-container .scaled-image-container'), element => { this._draw(element) });
+ }
+
+ handleEvent(event)
+ {
+ if (event.type == 'mousemove') {
+ this._handleMouseMove(event);
+ return;
+ }
+
+ if (event.type == 'mouseout') {
+ this._handleMouseOut(event);
+ return;
+ }
+ }
+
+ _handleMouseOut(event)
+ {
+ if (event.relatedTarget && event.relatedTarget.tagName != 'IFRAME')
+ return;
+
+ // If e.relatedTarget is null, we've moused out of the document.
+ let container = document.querySelector('.pixel-zoom-container');
+ if (container)
+ container.remove();
+ }
+
+ _handleMouseMove(event)
+ {
+ if (this._mouseMoveTimeout) {
+ clearTimeout(this._mouseMoveTimeout);
+ this._mouseMoveTimeout = 0;
+ }
+
+ if (Utils.parentOfType(event.target, '.pixel-zoom-container'))
+ return;
+
+ let container = document.querySelector('.pixel-zoom-container');
+
+ let resultContainer = (event.target.className == 'result-container') ? event.target : Utils.parentOfType(event.target, '.result-container');
+ if (!resultContainer || !resultContainer.querySelector('img')) {
+ if (container)
+ container.remove();
+ return;
+ }
+
+ let targetLocation = event.target.getBoundingClientRect();
+ this._percentX = (event.clientX - targetLocation.left) / targetLocation.width;
+ this._percentY = (event.clientY - targetLocation.top) / targetLocation.height;
+
+ if (!container) {
+ if (this.showOnDelay) {
+ this._mouseMoveTimeout = setTimeout(() => {
+ this._createContainer(event);
+ }, 400);
+ return;
+ }
+
+ this._createContainer(event);
+ return;
+ }
+
+ this._drawAll();
+ }
+};
+
+class TableSorter
+{
+ static _forwardArrow()
+ {
+ return '<svg style="width:10px;height:10px"><polygon points="0,0 10,0 5,10" style="fill:#ccc"></svg>';
+ }
+
+ static _backwardArrow()
+ {
+ return '<svg style="width:10px;height:10px"><polygon points="0,10 10,10 5,0" style="fill:#ccc"></svg>';
+ }
+
+ static _sortedContents(header, arrow)
+ {
+ return arrow + ' ' + Utils.trim(header.textContent) + ' ' + arrow;
+ }
+
+ static _updateHeaderClassNames(newHeader)
+ {
+ let sortHeader = document.querySelector('.sortHeader');
+ if (sortHeader) {
+ if (sortHeader == newHeader) {
+ let isAlreadyReversed = sortHeader.classList.contains('reversed');
+ if (isAlreadyReversed)
+ sortHeader.classList.remove('reversed');
+ else
+ sortHeader.classList.add('reversed');
+ } else {
+ sortHeader.textContent = sortHeader.textContent;
+ sortHeader.classList.remove('sortHeader');
+ sortHeader.classList.remove('reversed');
+ }
+ }
+
+ newHeader.classList.add('sortHeader');
+ }
+
+ static _textContent(tbodyRow, column)
+ {
+ return tbodyRow.querySelectorAll('td')[column].textContent;
+ }
+
+ static _sortRows(newHeader, reversed)
+ {
+ let testsTable = document.getElementById('results-table');
+ let headers = Utils.toArray(testsTable.querySelectorAll('th'));
+ let sortColumn = headers.indexOf(newHeader);
+
+ let rows = Utils.toArray(testsTable.querySelectorAll('tbody'));
+
+ rows.sort(function(a, b) {
+ // Only need to support lexicographic sort for now.
+ let aText = TableSorter._textContent(a, sortColumn);
+ let bText = TableSorter._textContent(b, sortColumn);
+
+ // Forward sort equal values by test name.
+ if (sortColumn && aText == bText) {
+ let aTestName = TableSorter._textContent(a, 0);
+ let bTestName = TableSorter._textContent(b, 0);
+ if (aTestName == bTestName)
+ return 0;
+ return aTestName < bTestName ? -1 : 1;
+ }
+
+ if (reversed)
+ return aText < bText ? 1 : -1;
+ else
+ return aText < bText ? -1 : 1;
+ });
+
+ for (let row of rows)
+ testsTable.appendChild(row);
+ }
+
+ static sortColumn(columnNumber)
+ {
+ let newHeader = document.getElementById('results-table').querySelectorAll('th')[columnNumber];
+ TableSorter._sort(newHeader);
+ }
+
+ static handleClick(e)
+ {
+ let newHeader = e.target;
+ if (newHeader.localName != 'th')
+ return;
+ TableSorter._sort(newHeader);
+ }
+
+ static _sort(newHeader)
+ {
+ TableSorter._updateHeaderClassNames(newHeader);
+
+ let reversed = newHeader.classList.contains('reversed');
+ let sortArrow = reversed ? TableSorter._backwardArrow() : TableSorter._forwardArrow();
+ newHeader.innerHTML = TableSorter._sortedContents(newHeader, sortArrow);
+
+ TableSorter._sortRows(newHeader, reversed);
+ }
+};
+
+class OptionWriter {
+ static save()
+ {
+ let options = document.querySelectorAll('label input');
+ let data = ""
+ for (let option of options)
+ data[option.id] = option.checked;
+
+ try {
+ localStorage.setItem(OptionWriter._key, JSON.stringify(data));
+ } catch (err) {
+ if (err.name != "SecurityError")
+ throw err;
+ }
+ }
+
+ static apply()
+ {
+ let json;
+ try {
+ json = localStorage.getItem(OptionWriter._key);
+ } catch (err) {
+ if (err.name != "SecurityError")
+ throw err;
+ }
+
+ if (!json) {
+ controller.updateAllOptions();
+ return;
+ }
+
+ let data = ""
+ for (let id in data) {
+ let input = document.getElementById(id);
+ if (input)
+ input.checked = data[id];
+ }
+ controller.updateAllOptions();
+ }
+
+ static get _key()
+ {
+ return 'run-webkit-tests-options';
+ }
+};
+
+let testResults;
+function ADD_RESULTS(input)
+{
+ testResults = new TestResults(input);
+}
+</script>
+
+<script src=""
+
+<script>
+
+class TestNavigator
+{
+ constructor() {
+ this.currentTestIndex = -1;
+ this.flaggedTests = {};
+ document.addEventListener('keypress', this, false);
+ }
+
+ handleEvent(event)
+ {
+ if (event.type == 'keypress') {
+ this.handleKeyEvent(event);
+ return;
+ }
+ }
+
+ handleKeyEvent(event)
+ {
+ if (event.metaKey || event.shiftKey || event.ctrlKey)
+ return;
+
+ switch (String.fromCharCode(event.charCode)) {
+ case 'i':
+ this._scrollToFirstTest();
+ break;
+ case 'j':
+ this._scrollToNextTest();
+ break;
+ case 'k':
+ this._scrollToPreviousTest();
+ break;
+ case 'l':
+ this._scrollToLastTest();
+ break;
+ case 'e':
+ this._expandCurrentTest();
+ break;
+ case 'c':
+ this._collapseCurrentTest();
+ break;
+ case 't':
+ this._toggleCurrentTest();
+ break;
+ case 'f':
+ this._toggleCurrentTestFlagged();
+ break;
+ }
+ }
+
+ _scrollToFirstTest()
+ {
+ if (this._setCurrentTest(0))
+ this._scrollToCurrentTest();
+ }
+
+ _scrollToLastTest()
+ {
+ let links = controller.visibleTests();
+ if (this._setCurrentTest(links.length - 1))
+ this._scrollToCurrentTest();
+ }
+
+ _scrollToNextTest()
+ {
+ if (this.currentTestIndex == -1)
+ this._scrollToFirstTest();
+ else if (this._setCurrentTest(this.currentTestIndex + 1))
+ this._scrollToCurrentTest();
+ }
+
+ _scrollToPreviousTest()
+ {
+ if (this.currentTestIndex == -1)
+ this._scrollToLastTest();
+ else if (this._setCurrentTest(this.currentTestIndex - 1))
+ this._scrollToCurrentTest();
+ }
+
+ _currentTestLink()
+ {
+ let links = controller.visibleTests();
+ return links[this.currentTestIndex];
+ }
+
+ _currentTestExpandLink()
+ {
+ return this._currentTestLink().querySelector('.expand-button-text');
+ }
+
+ _expandCurrentTest()
+ {
+ controller.expandExpectations(this._currentTestExpandLink());
+ }
+
+ _collapseCurrentTest()
+ {
+ controller.collapseExpectations(this._currentTestExpandLink());
+ }
+
+ _toggleCurrentTest()
+ {
+ controller.toggleExpectations(this._currentTestExpandLink());
+ }
+
+ _toggleCurrentTestFlagged()
+ {
+ let testLink = this._currentTestLink();
+ this.flagTest(testLink, !testLink.classList.contains('flagged'));
+ }
+
+ // FIXME: Test navigator shouldn't know anything about flagging. It should probably call out to TestFlagger or something.
+ // FIXME: Batch flagging (avoid updateFlaggedTests on each test).
+ flagTest(testTbody, shouldFlag)
+ {
+ let testName = testTbody.querySelector('.test-link').innerText;
+
+ if (shouldFlag) {
+ testTbody.classList.add('flagged');
+ this.flaggedTests[testName] = 1;
+ } else {
+ testTbody.classList.remove('flagged');
+ delete this.flaggedTests[testName];
+ }
+
+ this.updateFlaggedTests();
+ }
+
+ updateFlaggedTests()
+ {
+ let flaggedTestTextbox = document.getElementById('flagged-tests');
+ if (!flaggedTestTextbox) {
+ let flaggedTestContainer = document.createElement('div');
+ flaggedTestContainer.id = 'flagged-test-container';
+ flaggedTestContainer.className = 'floating-panel';
+ flaggedTestContainer.innerHTML = '<h2>Flagged Tests</h2><pre id="flagged-tests" contentEditable></pre>';
+ document.body.appendChild(flaggedTestContainer);
+
+ flaggedTestTextbox = document.getElementById('flagged-tests');
+ }
+
+ let flaggedTests = Object.keys(this.flaggedTests);
+ flaggedTests.sort();
+ let separator = document.getElementById('use-newlines').checked ? '\n' : ' ';
+ flaggedTestTextbox.innerHTML = flaggedTests.join(separator);
+ document.getElementById('flagged-test-container').style.display = flaggedTests.length ? '' : 'none';
+ }
+
+ _setCurrentTest(testIndex)
+ {
+ let links = controller.visibleTests();
+ if (testIndex < 0 || testIndex >= links.length)
+ return false;
+
+ let currentTest = links[this.currentTestIndex];
+ if (currentTest)
+ currentTest.classList.remove('current');
+
+ this.currentTestIndex = testIndex;
+
+ currentTest = links[this.currentTestIndex];
+ currentTest.classList.add('current');
+
+ return true;
+ }
+
+ _scrollToCurrentTest()
+ {
+ let targetLink = this._currentTestLink();
+ if (!targetLink)
+ return;
+
+ let rowRect = targetLink.getBoundingClientRect();
+ // rowRect is in client coords (i.e. relative to viewport), so we just want to add its top to the current scroll position.
+ document.scrollingElement.scrollTop += rowRect.top;
+ }
+
+ onlyShowUnexpectedFailuresChanged()
+ {
+ let currentTest = document.querySelector('.current');
+ if (!currentTest)
+ return;
+
+ // If our currentTest became hidden, reset the currentTestIndex.
+ if (controller.onlyShowUnexpectedFailures() && currentTest.classList.contains('expected'))
+ this._scrollToFirstTest();
+ else {
+ // Recompute this.currentTestIndex
+ let links = controller.visibleTests();
+ this.currentTestIndex = links.indexOf(currentTest);
+ }
+ }
+};
+
+function handleMouseDown(e)
+{
+ if (!Utils.parentOfType(e.target, '#options-menu') && e.target.id != 'options-link')
+ document.getElementById('options-menu').className = 'hidden-menu';
+}
+
+document.addEventListener('mousedown', handleMouseDown, false);
+
+let controller;
+let pixelZoomer;
+let testNavigator;
+
+function generatePage()
+{
+ let container = document.getElementById('main-content');
+
+ controller = new TestResultsController(container, testResults);
+ pixelZoomer = new PixelZoomer();
+ testNavigator = new TestNavigator();
+
+ OptionWriter.apply();
+
+ console.log("rendered test results page!");
+}
+
+window.addEventListener('load', generatePage, false);
+
+</script>
+<body>
+
+ <div class="content-container">
+ <div id="toolbar" class="floating-panel">
+ <div class="note">Use the i, j, k and l keys to navigate, e, c to expand and collapse, and f to flag</div>
+ <a class="clickable" _onclick_="controller.expandAllExpectations()">expand all</a>
+ <a class="clickable" _onclick_="controller.collapseAllExpectations()">collapse all</a>
+ <a class="clickable" id=options-link _onclick_="controller.toggleOptionsMenu()">options</a>
+ <div id="options-menu" class="hidden-menu">
+ <label><input id="unexpected-results" type="checkbox" checked _onchange_="controller.handleUnexpectedResultsChange()">Only unexpected results</label>
+ <label><input id="toggle-images" type="checkbox" checked _onchange_="controller.handleToggleImagesChange()">Toggle images</label>
+ <label title="Use newlines instead of spaces to separate flagged tests"><input id="use-newlines" type="checkbox" checked _onchange_="controller.handleToggleUseNewlines()">Use newlines in flagged list</label>
+ </div>
+ </div>
+
+<div id="main-content"></div>
+
+</body>