From aa7900cb80c972e8e1d0ab8e09e7ea8479ec3d3d Mon Sep 17 00:00:00 2001 From: Antonin Hildebrand Date: Mon, 29 Feb 2016 02:09:22 +0100 Subject: [PATCH] squash 'resources/unpacked/devtools' changes from 8893c96..2975ecf 2975ecf [DevTools] Add touch to sensors view. 6aada89 Timeline: extract and rename TracingModelLoader into a file of its own 2d4d494 DevTools: extract TimelineProfileTree into a file for better reuse 94e222e DevTools: [CSSP] do not wrap computed properties. 32c096c DevTools: Tweak timeline group headers drawing 8c6c1b4 [DevTools] Replace (un)blackboxURL methods with (un)blackboxUISourceCode 336beef DevTools: report resources as loaded upon failed request from workers. 21927a0 DevTools: [Console] do not replace '\t' control character b753ee4 [DevTools] Restore showViewportSizeOnResize. 1e5a8f6 [DevTools] Per debugger model state storage in BlackboxManager abd1fba Timeline: make flamechart groups collapsible b128f30 [DevTools] Disable JavaScript checkbox does not work. 880d35f DevTools: [Console] display control characters in console commands 6c90ade DevTools: [SASS] implement SASSProcessor.InsertProperties operation 03cdbcd Devtools: Allow filtered list widget keyboard navigation to wrap around 9edfc28 DevTools: [SASS] implement SASSProcessor.RemoveProperty operation 782faca Devtools: Switch JS execution context to match inspected node 33698a9 DevTools: [SASS] implement SASSProcessor.ToggleProperty operation a6d7338 [DevTools] Added Linkify.linkifyStackTraceTopFrame method 70ca603 DevTools: [SASS] add WI.SASSProcessor and first "setText" operation 058aca3 DevTools: [SASS] get rid of WI.ASTSourceMap.rebaseForCSSDiff/rebaseForSASSDiff a08075b [DevTools] Add "show throttling" to device mode instead of "configure network". 5a074fb Indexed DB: Rename "int version" to "version" 4c5344b DevTools: [CSS] fix CSS style formatter 75fb6f6 DevTools: remove oldText from SourceEdit 3a85dd8 [DevTools] Fix minor bugs in frontend. 2c24ca0 DevTools: [CSSP] make "goto source" icon crisp ff9eff8 DevTools: Extract SegmentedRange from TimelineIRModel to utilities.js cab214a Reland of [DevTools] Move screen orientation override to RenderWidgetScreenMetricsEmulator. (patchset #1 id:1 of https://codereview.chromium.org/1723663005/ ) b0611c8 Revert of [DevTools] Move screen orientation override to RenderWidgetScreenMetricsEmulator. (patchset #5 id:80001 of https://codereview.chromium.org/1711083002/ ) 69e96fb DevTools: migrate remote debugging protocol generators to jinja2. 22e4373 [DevTools] Make max-width media queries symmetric. 33be141 DevTools: remove FileSystem API inspection from experiments, point users to the extension. ecf45f5 [DevTools] Move screen orientation override to RenderWidgetScreenMetricsEmulator. 4a73f2e [DevTools] Add device type to custom device presets. 9ae3c2c [DevTools] Restore show rulers functionality. 97f87ca [DevTools] Don't load sourcemap for blackboxed scripts a58cff0 [DevTools] Add sourceURL deprecation message for scripts 9bbd93f DevTools: [SASS] re-implement SASSSupport.Rule.insertProperties de2e154 DevTools: improve SCSS parser to correctly handle variable expansions ba5dd8b DevTools: [SASS] correctly add sass module 4af0766 DevTools: Make JSFrames occupy all the space between samples. ae10e88 DevTools: rename WI.SASSLiveSourceMap into WI.ASTSourceMap. c8d03ed Revert of DevTools: improve SCSS parser to correctly handle variable expansions (patchset #1 id:1 of https://codereview.chromium.org/1713783003/ ) 7a2d28f DevTools: improve SCSS parser to correctly handle variable expansions d41f526 DevTools: teach WI.CSSParser to parse style ranges 4c9b383 DevTools: introduce WI.ASTService instead of WI.SASSWorkspaceAdapter 55f115b DevTools: [Console] wrap long strings in console 4184a0d Revert: [DevTools] Removed support deprecated (//@|/*@) source(URL|MappingURL)= 2cc00e1 Animation: Add optional specified animation id e7ca2a5 DevTools: Make FlameChart._measureText use Map for caching. 8e8a476 DevTools: Reduce unattributed size in timeline tree view c8f0ddb [DevTools] Rework device scale factor and user agent type UI. dc9e2b3 DevTools: [CSS] Add CSS.setMultipleStyleTexts command to CSS domain 6cfae37 Timeline FlameChart: clean up a handful of redundant member fields be5c866 [DevTools] Blackboxing in LiveLocations is supported in Linkifier 44eeeb2 [DevTools] Replace let with var c296234 DevTools: Prepare for CPU profiler code events turn from Instant to Meta. f2eb763 [DevTools] Support CommandLineAPI in snippets c8ceef9 Revert of Change Event.deepPath to a method (patchset #4 id:60001 of https://codereview.chromium.org/1637813002/ ) a19117d DevTools: add input event instrumentation test 4e76f53 [DevTools] Add workaround for closure compiler for Event.prototype.deepPath 6b7cfe8 DevTools: remove bottom-up flame charts 3487e2c Change Event.deepPath to a method git-subtree-dir: resources/unpacked/devtools git-subtree-split: 2975ecfa65011031083e64da05735d3d30e01103 --- BUILD.gn | 3 + Inspector-1.1.json | 2 +- devtools.gyp | 2 + devtools.gypi | 19 +- front_end/Images/src/optimize_png.hashes | 6 +- front_end/Images/src/svg2png.hashes | 6 +- front_end/Images/src/toolbarButtonGlyphs.svg | 10 +- front_end/Images/toolbarButtonGlyphs.png | Bin 8787 -> 8776 bytes front_end/Images/toolbarButtonGlyphs_2x.png | Bin 21564 -> 21473 bytes front_end/animation/AnimationModel.js | 10 +- front_end/bindings/BlackboxManager.js | 146 +++-- front_end/bindings/BreakpointManager.js | 7 +- front_end/bindings/CSSWorkspaceBinding.js | 13 +- front_end/bindings/CompilerScriptMapping.js | 16 + .../bindings/DebuggerWorkspaceBinding.js | 117 +++- front_end/bindings/LiveLocation.js | 16 +- .../PresentationConsoleMessageHelper.js | 7 +- front_end/common/TestBase.js | 16 +- front_end/common/TextRange.js | 30 +- .../components/DebuggerPresentationUtils.js | 44 -- front_end/components/Linkifier.js | 57 +- .../NetworkConditionsSelector.js | 216 +++++-- front_end/components/module.json | 19 +- .../networkConditionsSettingsTab.css | 0 front_end/console/ConsoleView.js | 2 +- front_end/console/ConsoleViewMessage.js | 15 +- front_end/console/consoleView.css | 2 +- front_end/elements/ComputedStyleWidget.js | 8 +- front_end/elements/ElementsPanel.js | 9 + .../elements/computedStyleSidebarPane.css | 20 +- front_end/elements/module.json | 9 - front_end/emulation/DeviceModeModel.js | 181 ++---- front_end/emulation/DeviceModeToolbar.js | 165 ++++-- front_end/emulation/DeviceModeView.js | 2 +- front_end/emulation/DevicesSettingsTab.js | 44 +- front_end/emulation/MediaQueryInspector.js | 86 +-- front_end/emulation/SensorsView.js | 17 + front_end/emulation/TouchModel.js | 149 +++++ front_end/emulation/devicesSettingsTab.css | 8 +- front_end/emulation/mediaQueryInspector.css | 45 +- front_end/emulation/module.json | 1 + front_end/emulation/sensors.css | 12 +- front_end/externs.js | 3 + front_end/main/Main.js | 44 +- front_end/main/RenderingOptions.js | 1 + front_end/main/Tests.js | 71 ++- front_end/main/module.json | 6 + front_end/network/NetworkConfigView.js | 2 +- front_end/network/NetworkPanel.js | 2 +- front_end/network/module.json | 18 - front_end/platform/utilities.js | 129 ++++ front_end/profiler/CPUProfileFlameChart.js | 13 +- front_end/resources/DirectoryContentView.js | 187 ------ front_end/resources/FileContentView.js | 180 ------ front_end/resources/FileSystemModel.js | 553 ------------------ front_end/resources/FileSystemView.js | 245 -------- front_end/resources/IndexedDBModel.js | 8 +- front_end/resources/IndexedDBViews.js | 13 +- front_end/resources/ResourcesPanel.js | 122 ---- front_end/resources/module.json | 4 - front_end/sass/ASTService.js | 45 ++ front_end/sass/ASTSourceMap.js | 247 ++++++++ front_end/sass/SASSLiveSourceMap.js | 212 ------- front_end/sass/SASSProcessor.js | 548 +++++++++++++++++ front_end/sass/SASSSupport.js | 187 ++++-- front_end/sass/SASSWorkspaceAdapter.js | 461 --------------- front_end/sass/module.json | 7 +- .../ScriptFormatterWorker.js | 5 + front_end/sdk/CSSParser.js | 104 +++- front_end/sdk/CSSStyleModel.js | 75 ++- front_end/sdk/DOMModel.js | 15 +- front_end/sdk/DebuggerModel.js | 53 +- front_end/sdk/InspectorBackend.js | 2 - front_end/sdk/ResourceTreeModel.js | 1 + front_end/sdk/RuntimeModel.js | 5 +- front_end/sdk/Script.js | 9 +- front_end/sdk/TracingModel.js | 41 +- front_end/sdk/module.json | 6 + front_end/snippets/ScriptSnippetModel.js | 2 +- .../sources/AsyncOperationsSidebarPane.js | 6 +- front_end/sources/CallStackSidebarPane.js | 58 +- front_end/sources/JavaScriptSourceFrame.js | 21 +- front_end/sources/SourcesPanel.js | 22 +- front_end/timeline/TimelineFlameChart.js | 37 +- front_end/timeline/TimelineIRModel.js | 134 +---- front_end/timeline/TimelineJSProfile.js | 26 +- front_end/timeline/TimelineLoader.js | 300 ++++++++++ front_end/timeline/TimelineModel.js | 291 +-------- front_end/timeline/TimelinePanel.js | 4 +- front_end/timeline/TimelineProfileTree.js | 180 ++++++ front_end/timeline/TimelineTreeView.js | 247 ++------ front_end/timeline/module.json | 2 + front_end/ui/Toolbar.js | 15 +- front_end/ui/toolbar.css | 2 +- front_end/ui_lazy/FilteredListWidget.js | 4 +- front_end/ui_lazy/FlameChart.js | 388 ++++++++++-- protocol.json | 188 ++---- 97 files changed, 3565 insertions(+), 3523 deletions(-) delete mode 100644 front_end/components/DebuggerPresentationUtils.js rename front_end/{network => components}/NetworkConditionsSelector.js (62%) rename front_end/{network => components}/networkConditionsSettingsTab.css (100%) create mode 100644 front_end/emulation/TouchModel.js delete mode 100644 front_end/resources/DirectoryContentView.js delete mode 100644 front_end/resources/FileContentView.js delete mode 100644 front_end/resources/FileSystemModel.js delete mode 100644 front_end/resources/FileSystemView.js create mode 100644 front_end/sass/ASTService.js create mode 100644 front_end/sass/ASTSourceMap.js delete mode 100644 front_end/sass/SASSLiveSourceMap.js create mode 100644 front_end/sass/SASSProcessor.js delete mode 100644 front_end/sass/SASSWorkspaceAdapter.js create mode 100644 front_end/timeline/TimelineLoader.js create mode 100644 front_end/timeline/TimelineProfileTree.js diff --git a/BUILD.gn b/BUILD.gn index a70c74ed4b..5c7c122af9 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -43,6 +43,7 @@ devtools_modules_js_files = gypi_values.devtools_profiler_js_files + gypi_values.devtools_promises_js_files + gypi_values.devtools_resources_js_files + + gypi_values.devtools_sass_js_files + gypi_values.devtools_security_js_files + gypi_values.devtools_screencast_js_files + gypi_values.devtools_script_formatter_worker_js_files + @@ -150,6 +151,7 @@ action("generate_devtools_grd") { resources_out_dir + "profiler_module.js", resources_out_dir + "promises_module.js", resources_out_dir + "resources_module.js", + resources_out_dir + "sass_module.js", resources_out_dir + "security_module.js", resources_out_dir + "script_formatter_worker_module.js", resources_out_dir + "settings_module.js", @@ -281,6 +283,7 @@ action("build_applications") { resources_out_dir + "profiler_module.js", resources_out_dir + "promises_module.js", resources_out_dir + "resources_module.js", + resources_out_dir + "sass_module.js", resources_out_dir + "security_module.js", resources_out_dir + "screencast_module.js", resources_out_dir + "script_formatter_worker_module.js", diff --git a/Inspector-1.1.json b/Inspector-1.1.json index 5a5a3ed7ab..55afa73e9d 100644 --- a/Inspector-1.1.json +++ b/Inspector-1.1.json @@ -1330,7 +1330,7 @@ "description": "Database with an array of object stores.", "properties": [ { "name": "name", "type": "string", "description": "Database name." }, - { "name": "intVersion", "type": "integer", "description": "Database version." }, + { "name": "version", "type": "integer", "description": "Database version." }, { "name": "objectStores", "type": "array", "items": { "$ref": "ObjectStore" }, "description": "Object stores in this database." } ] }, diff --git a/devtools.gyp b/devtools.gyp index fa1a131068..33505c08b9 100644 --- a/devtools.gyp +++ b/devtools.gyp @@ -116,6 +116,7 @@ '<(PRODUCT_DIR)/resources/inspector/profiler_module.js', '<(PRODUCT_DIR)/resources/inspector/promises_module.js', '<(PRODUCT_DIR)/resources/inspector/resources_module.js', + '<(PRODUCT_DIR)/resources/inspector/sass_module.js', '<(PRODUCT_DIR)/resources/inspector/security_module.js', '<(PRODUCT_DIR)/resources/inspector/script_formatter_worker_module.js', '<(PRODUCT_DIR)/resources/inspector/settings_module.js', @@ -282,6 +283,7 @@ '<(_output_path)/profiler_module.js', '<(_output_path)/promises_module.js', '<(_output_path)/resources_module.js', + '<(_output_path)/sass_module.js', '<(_output_path)/security_module.js', '<(_output_path)/screencast_module.js', '<(_output_path)/script_formatter_worker_module.js', diff --git a/devtools.gypi b/devtools.gypi index 7b940d2c88..b91bfb8f46 100644 --- a/devtools.gypi +++ b/devtools.gypi @@ -43,7 +43,6 @@ '<@(devtools_host_js_files)', '<@(devtools_main_js_files)', '<@(devtools_platform_js_files)', - '<@(devtools_sass_js_files)', '<@(devtools_sdk_js_files)', '<@(devtools_toolbox_bootstrap_js_files)', '<@(devtools_ui_js_files)', @@ -87,11 +86,11 @@ 'front_end/components/customPreviewSection.css', 'front_end/components/domUtils.css', 'front_end/components/inspectorViewTabbedPane.css', + 'front_end/components/networkConditionsSettingsTab.css', 'front_end/components/objectPropertiesSection.css', 'front_end/components/objectValue.css', 'front_end/components/CustomPreviewSection.js', 'front_end/components/DataSaverInfobar.js', - 'front_end/components/DebuggerPresentationUtils.js', 'front_end/components/DockController.js', 'front_end/components/DOMBreakpointsSidebarPane.js', 'front_end/components/DOMPresentationUtils.js', @@ -102,6 +101,7 @@ 'front_end/components/InspectorView.js', 'front_end/components/BreakpointsSidebarPaneBase.js', 'front_end/components/Linkifier.js', + 'front_end/components/NetworkConditionsSelector.js', 'front_end/components/ObjectPopoverHelper.js', 'front_end/components/ObjectPropertiesSection.js', 'front_end/components/RemoteObjectPreviewFormatter.js', @@ -118,8 +118,9 @@ 'front_end/host/UserMetrics.js', ], 'devtools_sass_js_files': [ - 'front_end/sass/SASSLiveSourceMap.js', - 'front_end/sass/SASSWorkspaceAdapter.js', + 'front_end/sass/ASTService.js', + 'front_end/sass/ASTSourceMap.js', + 'front_end/sass/SASSProcessor.js', 'front_end/sass/SASSSupport.js', ], 'devtools_screencast_js_files': [ @@ -213,6 +214,7 @@ 'front_end/emulation/InspectedPagePlaceholder.js', 'front_end/emulation/MediaQueryInspector.js', 'front_end/emulation/SensorsView.js', + 'front_end/emulation/TouchModel.js', ], 'devtools_ui_js_files': [ 'front_end/ui/checkboxTextLabel.css', @@ -502,7 +504,6 @@ 'devtools_network_js_files': [ 'front_end/network/blockedURLsPane.css', 'front_end/network/eventSourceMessagesView.css', - 'front_end/network/networkConditionsSettingsTab.css', 'front_end/network/networkConfigView.css', 'front_end/network/networkLogView.css', 'front_end/network/networkPanel.css', @@ -515,7 +516,6 @@ 'front_end/network/FilterSuggestionBuilder.js', 'front_end/network/HARWriter.js', 'front_end/network/JSONView.js', - 'front_end/network/NetworkConditionsSelector.js', 'front_end/network/NetworkConfigView.js', 'front_end/network/NetworkDataGridNode.js', 'front_end/network/NetworkItemView.js', @@ -561,12 +561,8 @@ 'front_end/resources/DatabaseModel.js', 'front_end/resources/DatabaseQueryView.js', 'front_end/resources/DatabaseTableView.js', - 'front_end/resources/DirectoryContentView.js', 'front_end/resources/DOMStorageModel.js', 'front_end/resources/DOMStorageItemsView.js', - 'front_end/resources/FileContentView.js', - 'front_end/resources/FileSystemModel.js', - 'front_end/resources/FileSystemView.js', 'front_end/resources/IndexedDBViews.js', 'front_end/resources/IndexedDBModel.js', 'front_end/resources/ResourcesPanel.js', @@ -687,9 +683,11 @@ 'front_end/timeline/TimelineIRModel.js', 'front_end/timeline/TimelineJSProfile.js', 'front_end/timeline/TimelineLayersView.js', + 'front_end/timeline/TimelineLoader.js', 'front_end/timeline/TimelineModel.js', 'front_end/timeline/TimelinePaintProfilerView.js', 'front_end/timeline/TimelinePanel.js', + 'front_end/timeline/TimelineProfileTree.js', 'front_end/timeline/TimelineTreeView.js', 'front_end/timeline/TimelineUIUtils.js', 'front_end/timeline/TransformController.js', @@ -755,6 +753,7 @@ '<@(devtools_profiler_js_files)', '<@(devtools_promises_js_files)', '<@(devtools_resources_js_files)', + '<@(devtools_sass_js_files)', '<@(devtools_security_js_files)', '<@(devtools_screencast_js_files)', '<@(devtools_script_formatter_worker_js_files)', diff --git a/front_end/Images/src/optimize_png.hashes b/front_end/Images/src/optimize_png.hashes index aa594ffd3e..25464fd48b 100644 --- a/front_end/Images/src/optimize_png.hashes +++ b/front_end/Images/src/optimize_png.hashes @@ -1,7 +1,7 @@ { - "breakpointConditional.svg": "4cf90210b2af2ed84db2f60b07bcde28", "errorWave.svg": "e183fa242a22ed4784a92f6becbc2c45", + "breakpoint.svg": "69cd92d807259c022791112809b97799", "settingsListRemove.svg": "ce9e7c5c5cdaef28e6ee51d9478d5485", - "toolbarButtonGlyphs.svg": "9cb447c4c5e8e65ae5c9a32983a6e515", - "breakpoint.svg": "69cd92d807259c022791112809b97799" + "breakpointConditional.svg": "4cf90210b2af2ed84db2f60b07bcde28", + "toolbarButtonGlyphs.svg": "fcf3159499e07a580dc9bd827d4f627f" } \ No newline at end of file diff --git a/front_end/Images/src/svg2png.hashes b/front_end/Images/src/svg2png.hashes index aa594ffd3e..25464fd48b 100644 --- a/front_end/Images/src/svg2png.hashes +++ b/front_end/Images/src/svg2png.hashes @@ -1,7 +1,7 @@ { - "breakpointConditional.svg": "4cf90210b2af2ed84db2f60b07bcde28", "errorWave.svg": "e183fa242a22ed4784a92f6becbc2c45", + "breakpoint.svg": "69cd92d807259c022791112809b97799", "settingsListRemove.svg": "ce9e7c5c5cdaef28e6ee51d9478d5485", - "toolbarButtonGlyphs.svg": "9cb447c4c5e8e65ae5c9a32983a6e515", - "breakpoint.svg": "69cd92d807259c022791112809b97799" + "breakpointConditional.svg": "4cf90210b2af2ed84db2f60b07bcde28", + "toolbarButtonGlyphs.svg": "fcf3159499e07a580dc9bd827d4f627f" } \ No newline at end of file diff --git a/front_end/Images/src/toolbarButtonGlyphs.svg b/front_end/Images/src/toolbarButtonGlyphs.svg index 7046181f68..779809e51e 100644 --- a/front_end/Images/src/toolbarButtonGlyphs.svg +++ b/front_end/Images/src/toolbarButtonGlyphs.svg @@ -15,15 +15,15 @@ xml:space="preserve" id="svg3395" inkscape:version="0.48.4 r9939" - sodipodi:docname="out2.svg">image/svg+xmlFYA|fbFTIdQ0fgrtv5QriKq$3C-3MyR`=_T|g zg3<#?s3I*CCG?yB_uaMLyWf3ptyyQCv*)ZiGkbpfx67PockkR}WfEWl0068e#@8(Y z04gZu{yif-r(D8ubaW;+0waZT* z3j3B>>*pIe-Vb>Hn#Y~6{NMsR`v=vZu~*8^K3cs-(6R$nrSz{+Q5huw^y#Vj`NR&w z+RP{5hyLHL;2aj_Rq^8piDLhN< z(r{r0&cdOzTWH8$nwfU}+@30itHEp`ICmY{pGt@{XPnH^P1DjJ zHpg8J{``oUZ9r$pTtEYRHMMKf#p)u={>T`2u@}Ot{ZImRdPEJvzyHG^&4Ufm=iCL{ zIb83Z=1srFYYd#}?hqAi+8{mMcN9Ok^;`CZuE@o~Log7^>dXpdg_h9<2=0&B^z@*rXNkS0tx{_`fWXp z&9TY5+D+>~qQ6V9ich=sRN8XISWL;ow<&TAB_v3GLKQuX>$iz!Z+21U%wv94a5Kwr zE0e39FZ1KyrsOm!8-2|tS_K)ZbFY95$^0WULFF>5vyH6jL3WXm1nnP{%aaX&K9Cq-RuTpdKW3kQ8W0&@HUD&n_NSn7#J03*odW$+I zEE$4z?nr@B<YjwHwvpOECqta9valx_5phWl72pm%~r+rHRL|sq4XGkOQp8b>9Pi|NV?PfGt!B?SGHtL;9uSB6C zl2v z-$D>-h>K%))Kd%-6<6x7@pG=`RW_u!V@w0ZI!by9Rox@&Oh9WKLAjQ$udin`5PJ?tGLA*h}qtxzSaQj%zC zjpXygkvMQv(y@1iz3E7=*=b2PG|y}q2{G6NSlPxw?4+?$`mb_Y3#u=)!~}ov;q#jN zMUq6uv;O*GFz%AX(+53;-mnyu|6FQ@SWkP!2L=5TmKeI;2R2&y)Ea%QzqTL_bU)K! zeq=>RjB*KWH2=$Grm4Pqw`eVo5_%O~#p{y9aD622+g%9mRJ?;DEH=h)_je168I5L8 zb`dLTNwFtG%)g85;*9KjEK=?%UE+Cu#SBPsAZ)y->B!hcrW3J2nQv7kfBU3F|O%A)&rxJ_CV# zCSbJmu|RufwtDJGhH^1QF9O&>v5qy2pHZ|q(vRcwp~0=p?al<3MsB+tfQgXMQbk|N z1246_oB@ob;vK-`4{q4NBPr&0dw*PXOp4WT8=~!7qCKj79Fgt!b-9d2do2oBwLA1J z8De@E^{--fZ@^EAq!M*g9sifJ{SWv1-@f-i!}T8`cvLMY7fZWG&^-7mKKm;U#w8)u zCnWo;vwDnh-5AEBGcwv4ierE=MpFeswbKPK?+(sBp{y<=Qv4h3Mw;%RFZK0W%%t)$ zc!5c3Liw+m!CGe?EGkdc5cg8(VUbvY_Ejv0<{$_!Cfaw$RfdKvYnwqpH+(rE;c2;$ z99>`*s1c1VjV3j)z=l`_VbTX^pnRawTFwz?g7w^$=u&zi%3DNM!lD964N-=@K$;Y2 z=l0YYDkEnHCWs;8_XvNJl9HN-hK4#eXTPRQ)%neZ8)wQ`sSk4Q#^ils&k&mc1SiqM z+@@aV8x0Ea#XDX`$PQi7tXyz56-@*1R3akEtjg+29?#Z|^kR88seai@`gd7)D_b*aGzddMpQ>9V7_cU4^KsWLSk{#Ctqk z7VH5`2r65)aZCX2pjUq<^O=A-PLlb+ECkLwnl4_it~}xC@UM6Lwk|8s9xhx>52Ljt zeHBNi_z>Ldx&6{#P8KG;p)7N!Gn+b$ZzLf3jnvXLR%br)q7=80#pTFu!rlK2+x{n# zGJvTVXqToT1#m^0H@a@cze)9wAq(eA&Q=?6t3j$1yJCdT*(VP4He6JB3j8c7Jf>|6 zFsZY7NqEn8&ccBG>Y_3AZPd_MYCc*>Dzi#72%yd45_qE95Od0oX6-OI1X!B{{CeRr zMN`j-W9|>E+HZQxv;krUA8HCZl~d(^0{l&&ENhBpDiGX!n`TOD&V?&2#ezRXpH(^} zby>H5`bP-+Jm+129_R<=1>jCxN~sj$^fu4gcF1oU+j)Oh=r(%WBQ)?SjV2jz`&1Nh zS(84Ee6?A`I{r3|Cf`=A^ebT;z~4ryd7&`? zsFgPL2(ZO$aw4jue8(}oZD^J{X88)DyQP|Uh*1-vy7Xi@1bjlZ5!FV+3LQDFpb|m? zf}=R(XsH~29BF0u>!#EDvo{DA%R&P9P6Rhrln{^cKurWb7Gj+RsEd;P4ydC{m2fvh z4ChSGg9L1WOqx4>pQFb|Hu-ovD^lg`e>2(Iv^P>uq{9YA_jr=9nFX1CB9IreL$9j& z(w4<5sC-y$ecHqMAAB-N&vY*Etx-g6{~3w3yZf|>T9*@3+jC{jianegJ*-<@M`t78-Ojj?&>dm`W(=d8_LEr(ka5B;ROL z5#Jw}cN{vT={fWH(Qxa@?x2MdFV8?Q*Fhd_*uh*#UdzFJL@{*h*Jv?w*x)hFA7gk) zqcZo;A1yN;xfW8hLDnqJE0WxvGt{3fF#I5LP~OP&N|4mcz?nz2^&{k+=pJs?-hwNu zC5KF|QC4}~7JLW25^ zR;p5#t3-7()LT^+lp0ccLoqwxL;C|%NAMCsAk+mar% z)~W{or^+y&nVFT3olCkRsyv+DlhNQdXFZw2{d+Qp8?}ahVP|14r>co!ZWaf%M{!Q% z05P+9_1qG_JSO7b5Bp>0iWbE+rEL%8>M$Qpu^r>5PwOI?^t6{cL<>qQ+;;F*7&~1w zZ_c&5o_P4piG$N{BaN0T$c27*Xk|^GZdt&@Z|y5aTE2__2uxHsTJIO=j0jPIY>a_Z zc+Iy)_&M{72w?P21q1$o=#``o_e!HRJqEat=7Mah3ObTLj?V`NsY8zCUk%g+=TTHe zRmhj#usdd2tR1m0rf2i@5$Cd2!Xc#Caj>D{P241?gm<#l*l+?Q4gWP$blG=PsnL)4 z$6_y_hr1?oyLCGI#u{2=X|*-%@tfYQTa>UE&`KR+6-&BA@%*6!po0n9-Jq{0wtarb z8=Q$>PLDzrMDGP}7_s`uwGs~wYf|N-SDm?hj{od9==O9KnPw~4^la~C2)i#gc@7Ia zsBw)KZfqVoMO$Na@Kqb^_J;~i%{eRIUimO6Y{SosGOL}n-uO&bt`hN@>!UYCAG_1~ z!9`PYi(h--kp&)Loc|(KjBT3wDc1f-yW>%C^}rXK z&rk1rTy>b{IdSnp(3iGa0ge&n0cD__AKF}~`|2sLacFukjf!W{(CJ`Y%iax^Vza8` z*Q}yv&vPFCXp@@tY8WQ*ZSPT5jmRBV+-g~z*7YDZ{;Bx-Ogf)*Sk%6(-s#pn0zc$~ z({8mMZ~2HulU>4p6`>2%slQSnp2YCskmET_;t~(GnGWZV-j!O^_ipXN2x?$*Y#H}> zU~R$!#Yt$N%7zt(6YWVb%mz;Sgj>-S(LTzA>GPss()rUepIWjm=$@bKJ+M9ecAb>v zI@`@SHR73E{mIPQ#dqAErBPJ3bm&XV(Z=$mMA(U16K=XiJ#Y>O*en6S*ju8b9P-FL>Pt&E`gvJA&V*< zF)=lHeMzQi1Ay^DQk|rK(Db<-sFnI%C?qw#0YpuO)@Z{( zxnsgj3~UMN2h(seAB}ZTBfONhOam3wnZ4}BVhlko#w%X5(M}b{%Gy*y625Y@*2VY4 zIc#1FW>j=+%;58FA)4yog<{vM+RhrM+VW;^FgDWeoQ`W`*&_*B^h4NLKIR!U5w}yy z?%cbRbAfs0^P2C=zNm!^87=fJ_L}NbZ1Mh{P_Mp$G~+cULx4>)Ex1n^@@5lFPue|; zf*_3QJq93&9gnv)Mcvv5nzqvcJVZ zN|VqF64R7|_UkR5?27}ARI?ro6j5S2%+$Y0*2isj9TUod`G<0TB=tLZ-wukZ;FR9Z zNC&|UH@*J_qk93!&B6VLafgyW_?^d#ezftpw1lZW?Nusu+<-XrU+0v66)m3mtz04T z*P?R49&;Q-SOG=15M4jWDA3+fh^p;J40f}yLSwS_H(%5qveK5-U%fd1DxQ~0C+bFk zC=2;2yxm{ZIj*2xJ^xZ^us26@%7bCTp7U=Nl_~kwbgWNZb0+(pYI-@MtOt71DI)o6 zG+0N*i5xf``^8X`JG)4}Z8H{}H!~+ilr`8|RK92|toiAvym3^%O)t+kMzho@qc>?d zDQWea^OR=zB-`*>rKm!&woUEuFY5i=HU1I)=kn)X;@!1AHxJ4+(|swHoF)XZY1gSA zAzqZ0?uXwKZO14YHtFl!nMV-pruII}=Q%|@+r3bJC>j$a6*&nK?Gj;f5n0sI$xcy* zj>?DZ&rctp7k|p!ejl>&Db5iiCHU{Yn*<7LH1#NGPc1WwJP+aSnskm4E<2o2bJ;Q{ z%RJ>*_3ZL)4K~X;UUZ@h*c;Z+XgfbU=ABk^{uh68)tnTS)=uCi!a5rD=I#0@uAjm< zbY2RN9N>QwS4z1nA4Y`}(|&jl6LlAR%aek?tc>T32kzJzRb=H`*~U1%YVcII?samP zXg=NZu4P`Cly#+q6((mwj#QJKJk?^isf1R$W~bPfQs_x(UIia&BCnmFoM_zq&ZcX=vd;Hv2D@EZMMi!2IouOIe23`)!P(J=yr&CnFmiCwl*zCEY(dKN+`T z3-@lddRGoR$*xr-AN^)?kS;AD>g~e>zZ6)*RP~!Jju(tfQ3+XjSdY;q2Q-(uu z>Ng29v%DT(n$oU0dF zwVCbw;CMyy(7R*Q_ND2b98N6YuOW@*ZU1Y2ZP9kJ40?rahsngx%M};U{ZTqS@JO4{ zS5020`wvG>kh&NANgNb~jEs8ecdagGJNq5|E~^5GB|6hM?H8;$?Z<<&W_!?r+xAzM z(m)MC#K#SnfBzT`^2cX-Y{Jj$5dvy?>RpUH3{l52eJ8ScZ2jB#!o>NLRhKiJP^BGP zI=!3BQCJPmnZAfps&cBx{2KRPs%kuX=MLN4aSTi40_qPX2On=O*3|dTAKy;iJ|Am3 zNJ;rKXm{@3_Db$wZpl)is(E+d2O|_IY9#(5>0(9nWTb2GV?Lj!%XJ~#k&~T+pI*+j zzL$f=JZE?M{3cc0ze0A_D+azu{W0v@^=;AXw-;8)Ym-P-p{K=L(5!?5q@$9?( zl3j2al9o*BV;^bl&%g!M_ml}XFTH2sRmAG$-lDE-{Ur_7aUqk4%DycFqaH@ z<{MuAY5XRq;+e63N`L8A=LG2tEckiIjv#}`o{Ko320C8AM4~}{xa*^HO&>z{(Ui~d zwdZoQR+vRzM%)YHj-!W#hO%?yJN8>+9ogI8!7j~w*%;q$9Jx(Z73l`cJKnH@HAQ6e z%(yf)k;RfoNLw0!g6*pQZyTxibPj2xch4-FEfm z-{Dv0T*+ev&!*AT{BRkLc6+F@96K*|A2ttM6+;N)`g!hT)DqR}8R`;BEVISR$NhIM z(<R=E3ibp3fms+zZE*R}*{%kJx6kqv#6} z+FY%;Ubh>tKbp6+Rw~(bFe-wUbVKUEHzRvi4?1b8X*o{VJ{uU@9maSDoP`?tiWu-7 zdjx7vwEfn*qyf}$b+7HOzu;y!>14Ma!RAmct@1gQLN@%%IfiYc25W3U1;n@+^q&HZ zmHYB+A1x*M{(n|wZ?WDB)4FgrP3e7$kR=39mr#GYO=n2dCK=d@mgHT#J2o~ZRdDHv z4rsMwJ=u+m6>nhtiS-J~95ITZ(d2Ap-Oeoh%9Xb4q{$zQHcPe_cTEa*^#Br#^8uI~ zLMZLLd%WVa2w@b7xth;XYwJaKtm^NM3otO{h=p;DO+H`?n34l+7+d8nvORMAVQbB| z#EDRKp;=6@sc4VkP>*dY*R6^mz5;b-cRfW#U1zz1T74c1XDN>^!}=3-H969H!!9*& z<#kQ32QGi%PfN-3&?!&N*=6T}L{_sVgik1*X;*hKool!xs43=tj)lY?E9f{MZKofc za)kmT88PSx_cLqTSr>95M{_JC*{_BPzP(5eooWSD6qu7eI}Nl^9!`8kOgXQXGONs_ zA=BY2THx7xUM-?Lq1G?gR1|abFO{v|_-?BD>z(=ufTMZHh+LwJQbSazIOxj#VZ+1d zNQ`)AoiY(=3?uDcO|s=g{39Pi}auRMrD`@aV3;N6OTZL+}u5uMpuf@dry-dGJf zu68Zh<&BM#t}1ToY`&OAJJGYH*HjKPe(3vDUpoar(ObgRB`C%Ix|01S+tY0%?Ic!& z6dszu-QG=d;42Q9TPjFXP zB1*6A%5*f}pK5=FV-keF&@gZCoM#nn)q^BsWVSHEBbE#f(A(7L^44GZjxl3&+T7aeL^U#_!K9My_MKj za78<51j*i~VwKNkgH2CLO9WI3zyQbRt)*Edx{^3?S51rkM-Q>n)$taVr%(~rJhIf0 z4)lLlKK_qH3y4A69WOTBdKpyD_BM4!%t8Kx!jgri{3&vx7qQ-|FTF*e-ayTDsNEXS*^A6N8 z)~R*u>Efg)<5xW}M;y~)Pta4V+}qGPlbxwcGk*lfr?3mpw5tws4AEgs<7_pTUsWpm`jgIO`98!a>*+w z5cxnW+0B5pgS{({{5p6BV2Fu*HLRFYnHt$?ld7E7ps+@7Qdbb=^%)soKvD~491Jz( zWJ%FSu2T44p%2kQ;4p^0(Na+~VJuuG;FX3sTj$xMdwvVjMZ~Y!{k$po9thsq>M4yBh04HLf6d@JVu3SEarfxl~xVpM<0N2}l& zDJsFL%BjAiV5GH4^v1F`(4JEgbxxl&Uw=TB4!)fhtCzui~kKBKRUkGkDA zMSnB9Rsecq(1G(4yC0Al+&$_`K0^*45VjR1~Z+dA2Yb&qZ3F+Z6UWg$+yQH;` z`oY0S6iIDh@CKmc8c7VA;}6(35+WcdJSxw{$F>^XgBAVVUvDaj_&Z$V+abYb=7UYn!!-|qOtu==5lnISn)_n(n7$i;l zUCK~?SJ(II06eDh^)7rgw&qxgvnZGu^nTC0TK}rPF)4ge|Hya+@*I~~j*!36Nxe6? zwtQ$vx}DD+ZQY~tnx3>{ZdGrFJwW99*EE2#`h@DNDCJy5L%5kKIo7vDhZ>*GZTpFz zNs>I?@YWwWWWv*esiXI`5asE3WYRblow@ouv9tv&N7UxFp5(axkh-4fq%h-8B7x=M zUODaFB~3PUpebNIneP^TKCyvGJPonkE?zI{#TCg)^ROpT_N9v`As9XuicCi%AgtG|jLYxhMM8 z*y#zi={chJTA3#<_0^c5^1k&_EtCo;oeKwSA&kbp(q*-OQ&`8$vj?9mi7Xem=j=|h z@k(~;!rqjJu6L9aIRm6FgkC(MzCjcKF8?8klU2$eas6}DZ4_{$vOH7~QHE^Vuo?wS q0IV|_Nv>s=M}G><{@*di$vH6>1UDD5bf)|v0x&VUbG_04iu^B-p24^P literal 8787 zcmaia2T)Vpw{JoVq4(aU_udIjI)Z{yq=hC(O8}*p5CRAy9jStdSgGPi6GBL&qaY}t zAS6^3gwR8O`Tlq2&7HY#?wfPw%;|fdHNUd=S|{_modqKu9~}SyV6?Kl>Hq)$q2&En zv^3=HLW8;}03gO^b=CN04DfFc_};B=$Co|qV#pz*d?2XrHYl`&Qq@TPL7-sBTPNd6 zGmo3$uby)G;a6|7vamF$eNEA=zj$-&DT0axtoF*-2ng&00Few-s>()Ndr8&x>${7U z_aiz7K8&XouD4DHJV`uU7_!EZ$Qh`C(bhwOoI2z2na^qxa1Cl_= zOeiE|Yd7hcu8CI&<>8^FVX~4QYk308sF&Wn;vEa&07nTAMtjxa01@#W##w9Zp*&B@ zspxRG-xM(H=q$VTYFJ^1$66PZPP_xdj=f)(>t~mDFXJu#brnm}Rmxd!5{)qSZaB`l zNawCq+! zOI2mwD;zk0GmGzNxcsD@T!XM34XF&9Bd6)StI-IY79F^;&4k1hXd;A>Nj}#;^>O%3 zBV+tQhqUZlA}CaVet5M20*n+?_&8vKbf&t;mXlOdjk#ASVD2Wj$T{h`#g4rqI(+6g zLA8}Gu2^vEh>_@)KljZq`{U2%%u^P-MAGfXcbC8Z&}rWU>H{5+c9y#vYo>*P0Q%#I z5Qp!y(WUsO;-msArp`R59QfzzyIuxOq>5sO_+DY39^`UH5$6c{T!MaJ;GCn8V@e7H zFTUEBBe(-$$fBne1!rB$a;%tTf+RQ4eWm?P5z7y`NGuP=jEK08Y-m9#^=;R)gfUsI zNm1(knw5eYxC9*(TMOsruOi`8&FM`v%{0v($PVhb-%7r|I2UBI6DA<`;#WdI8r(D{ zy1*F|s-L1}cLKtxI{g^awwL|~Yz#KRIoq(@MiMJgawt>(e`Xk%kRSZy)t*Zc%;U$m zPEf>lWxX;Ds9ebK?w#L;9_o`8k5e6E*z9o5l-hI$;+4yz6C;~#*=5R}>32M+TZG#( zG#Moltn@|pJUl44ixHQt+CSLsC{5wK<>^gti|bBDC!Vn(<9N$A_*Fi(Omg4ZWYMR! zz>a!H%INulSP+8j9V|V*w6KrbZCGd6#~6k`k9e#_qOmuwM`K;jm=?g*oZCg{=7>#H z(?Zr+5$nE_pM=0T^16CL?jx2h7*!%q?lb8uHss(9U5o!g#{Wi7Mbt~acIm8V&vDD% zp*u0Z-@f~bTEtx=x)&ypS*(h;YUb7FuNt@hP?qg~v!7qZ^F8``tKvx%(>_X z+dFQI?JTROLR1l&-p}sun|pG*tID#T{_{q~s^6uZW6;%~H^RysZ^clA6*ZJ7Lo$iZ zL{k^q0Mm-s#9)%HZ8Qry06R_OBT1goaAhr8E^4T*+v-T}GcUhpOh!?8rM(ydHA5KX zeBLneiw;lg9JY2CJRU9$rg0lj2xATnLwOC6yb9f;0ww$6wA`6qQtp^JkxsJ#v!vs(p$6`KOwQRXyT%?cwkzsHC zPDr7?UBM()<3HS-8B&qzj1l=TF%}a}G>Y17;i+P3Y8A3uU^?6zt6)%5M>w)!SnoM{;myKia!CGjE|fD4~-Fja(kTs|pzu&$tKfvXxP}E;cf6tE2g|&zY_N#4zn3 z8^0u%IW*YYcLJ~7WKL1QCslWD_q{iwbfMH1^J<+-Jb3d@tE6O4;`P z1KZ@F?8`aS7Sd`O1v|&0y5JcQo4!)96k&@K{CI@^jK9En+dr^|9@@hYsw|$Y@`5V% z%D|CBWDj3ba7Htkt)I0n&YT7U(pOd0!3#Zbrm;MuUd*w7ppac^T5-pa?mb}rkg!kk zhaj++zdCYJhl#K*+IQI}SMy02+Lqlb@QzK=^{tPEG;se{g!kSO(4sQ~eHJPRwhOk#HmnKADW=XTUZ@-V-ykA5U zPSKIlh;X#?b(MbdmU-EVFO+cS5)gH%Tvd~YPkiyGMmh_Uvuis`&6KZ8ax|y?6J9jK zPv&u=$Okgd|JQX;C;&%W3DD~7Wi=&B!T&W{y>vIKzelg%uOM00Eu#pcM1@xm@q8IFanW?V`>WhoM2@&?H9VfZktmc&Zy zWQiNJ6dfw=^z=Ezf7W|b+z4?o5g1WvV}S(#WR1d$vO_lkd~utyW6~CJTzu*9nT4tu z&NN?@pPye{R;KgbcQA)_t}S$q>8(@4k2|p@7cfU2B-=+mOx6@2jE-N>`->{KxwjT0 z-ntc>({wg7nIVoy8^tg1X#~}WE~3v{TZm@yEGN^${NdECs>N^K9wW*S`h4A7RU zS%P6~rd@b0@j#gx{>SvG+VdCJD(oIn!wV&^y%-K&@IU*)hHA{hv0xM$*vHuMhT;#v zYuHn~K{H2VO79dOoX3XcH8U{i>#G}f@`z4B{3YHP^shO=4M=?QsT=r5%H|3r?hc*; zZ9z@iMJsIs@H!U=SYJ{jBT9=05t6hys-Uux>=0_Tec1;@kMze62xL67UOABSX2jo% zXg_<$7)fNXUvM@_$VhOwTd&soGIno+tmQBDJLjOZ?$R*2!8l@#sRP=~#f z!W$fnP>CecWXqYdkl77nioh!Z{XuvU89{v+O|SkxNJ>#*b)YuSCZlbMAn#=gt*D1AQbTKBD?|SB`EKErO+B|6LcnnjH zkcr98;j+=|xw2^N%>+F?w+jWtFTxn1P5k&rqKtUF4d691{vz+=W#97h*Se$8ZUv%q zBc;nc{V66JL8)!(>72xk6~-(a=?wu6(TjW-QCjGXR3BLu|7oa>EZbDOOVrJuQ|U5g zv%vKB2{BR59{xp8l0q$gZh@h5?UetvISpX)0Au_7p=2yn<$c_!zexUgQYW}<(!p8a zAM63VOt?o~F9$UMzw8Fr!s`O+DwwTSG$ZxoQ~WwDgE6-$r}>&a(;tUS`mL5Ixoy%D z;?G;5TpUY{2>XoXh8D#*PLwBgjkEEuM?Ax8x|WY;ZnexDO(S~T>3b2paf0wb3NnJ6 z=4*kQLX6OV7c(0c#V-M@C=tZLxig2^aC*@L)sp|m(Bj2>;{68K_UJ#S-@LZQU3W>f zdahxS!nyqhZ7r`gj^FDhuD{pT3b-+76a8*}4I-qLQwB+ld#Hpa`KXVjnY|PIbS`PU z5WD__Ya4yK6@eJ1B~myeWs@0HFE`sxpo@)B>K*UvMR zQR~GW)cmBpNcyky+W6XB$Qw=Hf$S98Im)~Qeay1H(n2z&u^vlJJiu*N_I!5{ZZS9X zd9C4eo6nD-uI!4@)w_-_Y%*zjo}f_};jjN;j=?Q!9NDQeiUHb}bctsvbue&m|ogQ@?xR?UeJDN z*3#xBJih8{E6{a+BPwROSN`X$4&&-@p^ggKU2-FG`=dDJO zc*c}7!>dqQvHBvuTqE7R^NU)Sb*%4ye=7;ZfiA`F%w>;{9=V38=?|Rhm*lEIokr`yVmrbT_Y{1xB$J|~;hmp|JllKjkb#?+C(#SB2dT^uOJ;o{rtrVMa7RSL%ZIh` z<42Ryc7~I`*M&m$EkBja?ko?ov(tMosQzj+~ROMcnpFO6tlK|+*-&v zAO~oTr#14CN|AFdP3SAHYE3cto@o1C(6qSFvQBzgC7M3pN@}Ijh%rF(`JL8^;yFJj z_BVx&88HlzBkNo_YI=9OL`Pm5tG|VS6x-mEKh%QQa(4tG{k9NpBImYRIJavNyo66& zxgftBxdCJAiSS|cIqY~mW&tz0uv}-qL;O^jyB=cR{LV0k$E35?Q|gW$vc$h5chF~# z#6!vD5$$0oAJV?hYxMBqV4;TP4)4}-%d9>*dbS4@F!XVn3vKJa^0f!XyIaZ!)u76y zRVuV#lIcfZU?6y-Jz(9$$Kny6S@pBSVd6*`A#r~WSh>Mp-f-K4gWj7QxB#OGV7Kgs zwH`tzffbtpQXW@~}xR zs!uviv!Tt&(a85RUF5C#sw@5(Uz$or;nfBixhnX%&(W*1grE{Z7udDZeG`d`v8UhkM(E9&zhs5fW5N=-kA_nP?M7Cmt_GEH+;gY%C$B ziIIhd^*NJ(Yohu2ug8nkR&9or-$X`y2kzV%P+L#>sUA1TtnfQWSg~f#xmDqp{(c!R z?>Nsxh06djiO4_ZNA=oILOOE?3k1YV`VuD^g?f*cxME;t0!RVH97p{+V(&+g($oxZ#C+8@pZg|VN@Vo%z zL@tH8)9zYO#tb{!f4dC{gc|qpD}7-ezkG6sDW-haQ^2HqJfh+>Ci^H>QP?p_UeV%| zaA85G?XGqB$0Cx3u_YKiueiOevasHnko~%}+}S<6|GcUkcK`QV-fdlcCc9KzFuHsE z9P9-;JK1Q8|Ib0DJIGzj&TI9CbPDk&Kq*BM1+)$S3Sh$V=gqyepDrh))(SN5tmx>^ zxX5=l#5-Mh#S8D4GgRRs59~U;K4(7|b~zVr4LPt_(+eZB?_}D{wE_0oBZlzEi}U=D znf~Etbi@MIQK_oR|16eT2qi95gdP!%wp6@W-sW!XSU(gai^&l^B=R?e@WHB0Wi4G9 zL8ctj6n~x`)M$F7W}GV|DN#H$cCYT=;l;8J`t7eqgVv;kj%Z`=-UnJSndE@1|7sOk z+FN?4q?x>0>`cSj-vm^Oo7tDYm}eOcUB(?8$qPo^ci{)o$IrRn`YJSknR{Iz+_kj+*+~@DA5aw}HsH4}6R_XSQtNB_pJ4v0rok^nK zAi)Jk%wiH7(c0_Cy;i!a;}2R1uUCE^y~>8s9+Sw1Tuc(+9BFGP68u5E!}}F54e;Dm zV`Lx48J5XXG@^egi0X&04CQN_JN0f1@@4gfZp)JXG%hM+r~UOEZ>nh@;JQA}8XS9> z_vrSz{5?+0=oUJ%2BpYh(Q^%?7541c*3X-&Yn0?>81Up&Zkr^85A(Vw%a~G|YMvSV z`L&N6A&eHUs!jh__wY~j&zCRqoY5*BpArvcJ8(a39&R@O^oYnC_%v%CwK(kBn&nPS z{4pq`v`a%w;aqgQcbO!6KAt>!6l*5|u{iaHgg9pUY9JhA1-DhA1<8}l_+tzwW=aEb z>Gd~y^s4Epb^E)(_~1VB%%!`kC(C-_QcG|?&t3xds3vibi^Z(=eSpJZjnP2m2o;gb zg@V4n$vf`B7AjI0v$gH#Xo4w&^UqVxnK%~PupItr_tT}|4s*W%x-F31hEraG76t%0 zAzk(?$I~Qy7hVS5l@Xlp?$l2>`cxJ@n^n3_)tvPWa9D5_pd&4cHz6VJ|J|gsBt{v4 zqe*|9DP|o`{5Uraz=O_)o8O(yA3AeQZGTASAF}F@XutpI$Kjdfk3CmSmD66gBp1>y zS=KQ-xR@k{K3q(t7pUG|%@K`?C-lSV#7Ws;L$Fu+KV8mUEAnuP-G3d6PrJV$V!-DDKiYK45Hx9iD+LIgJ^kZ4|PQ8JuS&XHM^! zzCnGLG~SOzTf4;o9-AO9Q=DjJl@%GisVsnI9M53%?z%xCs-W;_>8`_IEgaIVx%sxy6D}|>ol^+O zmI0>0SjE572x15O0x`f!@FFsLbli<*I6|qCA|AUopunnP4r+Nr1sAsiSlGh#sbJ06 zl8-%1CL|pfir=(b`TZ&E@9^c?N@(*n)I-t{%EGqUeQWRGb+PltyRvc&yI_oKbAC8Z z&IxbGRMF?*R6Wx}jLIO`_*}(8#K`T^f5hm}IrV-cw8Mo3sE3ym3f--3mp1QnqWwAb zuOQ_|P57|lFZ5B=VBwI|nv{cwmzwhbU@sX!;GX@OL z9b2T^=%V+!fz+C1nFer$rQA%(P5-?y5!CN`9JLE3Xo$*NT$dQL+Z~yC!B7y=v*(`o z_jT(6J-vb4r+Z~3ws!g7?z8`@quzJmMl*vwm$Va7nY|gIFy2PMFu&@W_A>MeBhfX|Spy z#8!(*Oc8E8d4vZ94oii_sV9lb4+a9Hj!?5GCcnbsf<>plV915@y-ABoB2K<9#^8nenDjwgw>+r!05mkHvgP_ z+%~s_w`ArDVztdFwZV1OfAeGsQ7)r><2{tqZ5PoHkyAZ^hKp!pWNb5cH&@jiYB*S* zf8ZB?{5Uy9LInerQV!emZm_^fVR;vJaNbR>>ww%8zp?;O1P_IaGp388if;HV)t^AgU$QPXT!$-)D6{ycUJvllt zpHD;a=A5hVeihMU9lVI4bQy|-TfIpgolr;MA_U>O34O9!9HU8H2ID|(oHJh265`2R z)|3-l#nSk``ZCW1!u6K# zce73i;3d|oSy=zeO|v2;f<4jIWphbQWrn{4yg7b<|KXyb9dhoY)nrDCtT70@sOnOQ zW%-O0eF(Lcm8uG|?+_LSfJlNnr=%70ca-jt(V_>D}!imS&J%2Zbxe9zNa~Q`LrflbW zDNG`R1-S|r0C3jYdL<*b@Ul+GP!Iy#z^KQ_kc$iDD<(c8CZvG0Ch6^KrNrCZTt`=#Z_=#{ZkYpPZ<`FWL7(@@c014!Rr20X)@9?_% zg!a)35m0i9;Fx{(R$cV;Rl6t&?xAQTJ(8!F*gq5{F-O-HQ4l4dicXoc(wbh19f4 z+FT6+yRl25E@{fzl%>?G4H=;oY({AY-|Zbw3Cv&Hj?%@7X=lZ_yCvXQZivjQfEJXik+6=nb%1l1jtA| zA%e6&-DcTLj~7QRA}YMyTt1Wm(_t(v?U<@tB4kkwkHFsk^adG5D!4pgf^zs>05*p{sByr^<;_mV}+!yUn{Wv5Ymzi{5@ppwZ`AyX% z4Sa_>svjO#pxEZM+S#>a1*5eg*`;cjIwB_kjWm~1{yje0>_7As`ntiNGd^M2FmYGX z3xEM}jmQV$__=1Dt&Eb=6}W^vq6Dk%0)lv_5vSm91ks`&7(%ti2-mNDWLbEi0tn+i z#l$tM%`-eC_?0~DP0I^8AQxpZEL)V31c)WLgWs-%cug{y4BWQ+*P`q@Zl474X!*U| zH)rVKJZ5mAjkaDBKIzFnW~eo#+hC zBfs~&-*?V+zVn^yJoAT{=b63sUS+R!uX~%18fveJ@agbDAP~_T#aCJ&5PByFgr+r^_EIr?4PwCh0TT#@$sfs zQR@5wu`%>gzZb32j9(G=fH8Q-(vrqT@<=2__hN91rNoM^yd|;U?bf8H!3!G+I>KVZ zElhZX^@&{eZPI$~A~Uu9o_lqh=a;-)!SkEl1KY|2qdJe=jfLZM=_-fZG!ZmlRsXx| zp}0G=ZCm>*GM}yidk-Nwr!d!t2Mn^)YTKq-dz$1pXMbI?uvk_>jse=#3W1gml+mDN zrEv1>Q3y_OH|%bM@mN>Zoc!z$Fay!yE0I@ZCT`&g@&+$uqy_x z{Tt>1^P430sT^$z0cktwbRnB?{J@#EUWWb$+vZi zZQ7>A?RKWt)m&OIC*=v2wThvv8#a6s>#*F$7fQ(Xp1zx@LC7Iq5pn2$zMeROr>{8D>wlI(JT8{w?Qz~>xrlGk z>5V~4UrEC>JI9|T8qgK4q%9FYV4*hF;frIpHLZ-kb@ut)z-|+DUK1@&RbhxVa@l5R zow$e{gEs9g!=-Kh_uwqv7Z<#~s5m!CJjTA=I6$?fwpL#2>-oUAe_ zStq{}Gz4;>Lz=H~%$pm(5Zr0Ilg-KLLlfQk&Gw-Ue=;0h{{UICDMRBF?xV$p|DN<@ z^s-GY<_SZvRB^Vx9R?(3w%jDazclugJNBy?k@5MQy}}$ck9=oOS7?hSnAU$qYJ)i& zy$j?Zo|2VY@A2U(x$Y`OWfbIal~Q+Q(Lyya!|p@*g&;o<*39vkKFas6mwY#iXp#Tc zUZZB%D#x1AMCF2udBU>oM7vGB`j$|&7Wob7(4ldwCPD>^n6Q(ub6sJrgsqY>_xFHd zR%|z@^^KT)BmSh?T7|xDj1mR4;v&&*z#KD2ntm4l$B3mDt$rs=U7`vS)g~WZ8ys`I z8@_pr&I%X$DOF##ia$MLNndWXD{An=1Ytw!t`?+tNb-$ES3ij5gL@~&A@-V* zU?J3T&Sp)i)bZOHZ3bkH2ynn50WZi6AG1_C)Q@cbdcw+-d_AVR8JD|@oz#{y=>zU< zU;zHBC3T78Yi(;K2^KXofrdEFF>dZ)%L^>(E?#E|uTM%v-kpnmVEgCqhHh)#i#N&uA~1L{cpGI_gzetF z#I;O=>hua;<(16P8I{v8g_2!3{@mA2{D7NV4JN)6wp(w&V>0)v@?2cvcF#guSJOI{ z`zcj;(vx&U{v(K_dr5}oodo_R)+rfc+Gk*_es)2=~5+&xap+f>}2=>yH7v+~_&F@`boq~Nb_IYmI_p;*d zbHu9*JEkyH;s6lkZS@}0x8A{0U`bP}z{=X{Ha*{fN-lPK{}pB+xVwttML0bv#Vf>h zzuS~a4Culn8f;+RDRq6iuRjJzfgN-9b7bwR?X$YY9UN^>;m@ob;w517k6+kbThRm- z`3gcCI+=w@%B{5-g3sq>3$CMGpLoCdUF}r6-#BG*RBvPAK}BapK|J!6#=i?>aw>NS z3*hs!x;uUG2FI~7UdYSyxwnE4OzCZnP>ztr0d30Kf>>9t_qT=^`j$`g5L_mGV;w!!$M$ov*FDvhj87i!EHYXI?<4#kfpk9n8&~uv zB%AJ;D|NX);*EQ`aQPf~r_nKcDzK7PVu=!=d7~jw7GBA=hlpuV6%71D+yRn1+|X&& zsP>+Sbj73BlxWbS1W-xfQ{;}s^CGJT$Lc-$C%}z-wMu){j94ffS>n;ktFYIYP!;LViT?jN3fA6~GWE(gd0R0JbvmC?pAMDj}Jgvgzd)@vPf90zOqi85nSy{>a4x8LzcpvE%+1mvN!^KTJ{^NL1Y|q zW|Rj2opS9uIz8^{uR5E6aDHeNQLPx_8}{#UohYL+qc%PGgSqv`b8NQuLkJdg`VM&|&=&`ujjmoXWkUGU-c7;1L?Ebc$N;<-njzj3K40gUYgj&_y*KG~HcK~G zf;=;{vA5C_3vVy5r(xJL|>^VXP0CI4@Nj1bwdx@jg7N+}G zZzV>J>M|acy-%oeGR9EN(m>Tymof*Vb6AoNTj82c$6FBdg^kb{$8pFNwolJPS` zd;9w7OjR<%F3yQ~zzXGD9OX|F5sv+BLg5a36B)%!N5p3};@Z6-Xwx`^&qR9bv9&gP z2E4Wq6NdYD{+z#98yqtoG$N9Z#q&R17P*3};qF0s$8-6v!muih4VZ^}!M4^MGToIz z&}PnKrhTtr65sfR?LmrW!4E1L|2$1bxCvuJwG`%t4jd)2)+$+Q(GA+gxG>})j-zBk z*JdrM(#8RRD2`)Z^e59w@4`+TLHg^>fqFO6|SXq$a) zV3&QaaePv|f6__M&}TG$g8HJ;z%((ge2I z88K#!PDJ1rt+~ zQ6F*&iH)>!teNV&Q4e;EN%yNjUz}5Oj=8Il6;NNB;77tI;RHyb+rzAyo`%V=29NSK z{gH(in5xikFZK##bd^Fp#lJ;DKkZT9ltXL1dnyOKOzq9Gs&n;5cI4^fX#>ltFb-|n z@0K1l!K&VR=^@H3W*zD8#t#kIuP{;kx>W?NLbvw$dAHn}wlL(`om-mcY)|qRM2o{7 z@(iquenr_dbuCvdm-2#Zc?0`sS~+MB{V9;I*B@29Rb7w(=dKC2yZMt&sh)Qv=& z4mYEUpiap$GGb-w3`fsHHI#p>zs|#)GpcP_c##^V+dS!Q?h0MA5%g*0`TlVh*NW%r!Gw{Jq2WQu_$eQ%kokOn?Xn~J zxP{~3+xd+(%EDIw!-Wb0T42~ZzvEsjna<&O(5ZH(J-?dAWMBE=Qr%rbHDyP~ImVHL zk`^J&rO5@2F)DS^a}xi8n40kcYCn$#t&sLTrbJp@gxs83Gstzg>X{vOY_{F8-Bol3 ztT(H-nMb%Z6`BpD@yo;&>DnF!;|Y|a^&bFVE<8xe#msC$dw!RlG2Ae5jwVE9$; zPX5jZ(b*u?(5aeerloqTTV#Evnqm$j#%vORKLXpIQY}2j^m~egA(UIzcQLMyCLkJ#!k5A(R z#_pusmfNtZp_FC1cNHeVG=W_phAMCOS3omYD+DJ4aM+_J=W+we7k)>^+6+I};m!{v zuzvA^M$@G@up!k`$R7=+dJ+eIH)8JDo@%dyMX}HcJ8OOUlk?5Wp}n9X$Db`a&(~o) z$bIyH6MwlUr(SEVXRvacGa1rsbaVd^c2e-do^dUS9CGDetEA(y_-Lj?OMbt-MAsY2c|GJQwJiPOt!r*Sg-CxwnaXijT=KMPa z@#~^k*R2N-DG0k!=GafQIrOa=@KtvS{FxTh-rcWMqG@-IT?-W1bNQEd{*7*5nu%sF z&rT`uvq_HW-J|_(ID01}&tzx_I{^gR-l4;7I7=OQsoD91UY}XB_V2-YheFDZpmWTe zj`Ro8@azNAx-p$$lI|wsw&m*qDldf-tXs1-e@#jJ3^baT_%r8DYl+2vE0|UexRM6N za)+u5wWth^DJAC^V?Y4qmkBs^a-Fd5PEkC`YEI8Pge%w#QO%_zH}GUr-aV5XdkG38 za-y!I4_U5w*A!F&kY-Ocms%!W5%^d#VOQR1?1~@U#Mav$MM9Nmlx3R=6PSRy2W@Mq zv+I9!sN^>GL9|Vg`y9-6s=3i~|3$M_U3g1OPm$d)w(Ms&OFzqV)loB*2G=LZrhYlq ze(ShKegZeDB*btLvH&%(h%yP`Nnrs{wvRZySa>?{tM;BY4?GX{we>F9r;JXyi*z>t z_e@5&%6);3Ue|8DEZeBW2x;VA(9-v{Q|fJmVe0vxP4(xShXRqX-5 z=T{+~A_Zhf%59#nIQZFL{}N;8;Zu_=l@6q2oS{*7=@t)a^I~?4c9g!$z?dUH&5Wx1%S9D8eD_2t}P*YAq{58JKCnjbTB?Ey z4vHo)=<~jA)lfYN(99sO;u1)@>L!w~uv=a~6>YpL+O9=EC-(;<%l@kZPKa%r3O9k1 z4&O(XIxMOg?=TpDHMHK7kkcx??R^p6p*hNK+a}h<)^U6IB7a-gMNPk&Gi_=GN{n$R zi23S+j0){r=EH~4aE~x?@*~n#Fyk{{>e51>tk_RuuV`57gbb7wL&JzF_O@k_#SQ9z z_em`q9MksZoa`%W5N>B+TB^(vJSJa3bydCBL5Of2?um{wCSC4oIcTvG88+l$5DB}! zjea{GVdsT(5X%KIRa6-K_021ZQYQU8tzMJQxmxPe4HDa;R4qpC(aD7adn6WI z+qBF{@Bb0CoG~FdbV}!TH}D0`R3Dc&z>d&+4n9504RA8820>z6txNnm*-7I$kHeSl zhI$36{+QV2qQ$@7klQOxW0;@2pNG%g^NS83K)U-!> z!GDX7f{iWb>SrYf9bzh=xV|Oc<=%?T{5TN(LNcUfh2v9b zAztNtR@3H!7DG<~!0M2dO)H$^A5|O=4 zz8|lLfaVg_#%zC}cgR9&xhEnfz{VDHg0qsx4ly)HSdwnqY{e~jSijv|2Ej&M>=RWD+!&UUZ#Q6VT z9z1mU-!q~L_tYv(Ujhmw@E91AF#+%qS5J-T*@pY8Q@|0%2*clJMjehTt=JC3u%@^8 zpumi!=QJy^uDIj>#bQPcz&~0!&b@PvVKL4EkT}4SU_&rj|F5*9CFsE|7HJG~xraZC z0ct)^NEMB+6Ls4&1mkg;?a@Q#Cqiq4P~$Ayw1Jn@($BL~+GWzw($Vg->N)d&13tc6 zj08#y7kum`IBh6xAT3aQhCtc47%s{GcaBe*50nSr=A0k6z*(Xs>b@t1AV4+b^=>0r z5Z7U+*HuU<>iNoBCGwpa#laMKm!Ey=hLF|;c#7F;f@ilfypzEEH2;Qdw?>#aWUQWF zFe4x7#&Rw6@Nd%u_KXz}bK2f04r^+k+ML$7PNuiqkNWf9qb7aMFNaf)ByMwz%Aa!H zQ)dzwE(VhMCScFq-{y3j#CM4)LIIuG^r!cQxVX4*nzpv~IQKzQW4X#HQSidUA;5Tqyr60zL#63+VjOCQ z%dA!Gv-`)LtN!uyYjmA_h~biMQ+B{<%P4lansku6dpI{gkMdlgiX(xwz;7Q0K`D7yb}Fh54n%p-m}Sd;k1E0$b2SkwdTys zOgT=yip?y$3)6PrOA-EzA-jd6+bnN_QgmL>Ya$59>1f29K`T!?#@e^$FV$MBkls5o z1{%QSa&EG>b+z{P_7VD(M(#ef%Z&)cY5D9rqSew5%%%J84<#-(BoOUGZi{XVhkm}? zwi7~u$}Rhf&AB5$Pi!9@O)Uu59b`jlP;d*&c6D1HnlW<#ZQCLPYeE&_$(%pIr({A?*NlIY8-FuGi5q1w?)k5Xee|{G`VOG8*kcI}$$5Zk z{THy#+AmU$uW%3sNcedf^E3VYw28{?qJ;n(Cf&SM$xc$IkUkJY1SW=y$p=y(BJUd^ z+wu-A)`7KCm5(?yIq>#B5~}qYJaEslhrFN=9CKcf0OM8V`3rousmMsI#!$79Y3=VUoWr`j(IbxhJ~$4G ztq-jMiV{1U3IiK5ZNj&=jRfjYlgtz35(!YLPRZ_;1VAD}LVHFH4l{RRI~8^9uIm}L zPH!iR;^^t=15BD%aA*S2?zIm$bAP`&p%L^o@BM#{JG4pM;FQ=9|5^-`8xS`FAORG- z$W|VpJxv@n4TTc!4_8P!8azJ#-1@yxV&H?krUC(E0UjU9=(YZc6l1q|;T7^JvU0Y< z$)|F$;T;&G!DcmHGY#IMO9D%qIEI_~nO17wXEYs9Pqp~fwr(7qCbu~&ynhlX0RrFU z$wQZ?GR0KkQ$7X?KOE-3bBzu_atG2@2ec1ab_!SD(+h!4)74L1InX;2fPSEXX#A-_ zIwT-K93bKU_a)800gnLAb%r^_0U4b@GeFo?h67AJl|%xZHz6_ZS7AUCjFwN=)UBHm zeU+G^c@&gYAbOij)QJ}kX}@ET0%0$0PL%m1*X`F%6{2GY4txV;LvYuxM~_~A>RR{N zeia7o0kulsOiOwA>>211K_^_@6U60)KcO~6x2BRhnEROv<=)_A;)XAgws(yDxhU#` ztW4Cj=pi|DFtC>>km@O%ztkPG>dSP~%6Q6gCTcwABPJ}jjklpfrJ48s#9qRb#@So~DJgnS3h7ltQi<+IEQbB$=lap)_k!;Z`EL&yfC#)~Ncaz)izf*SfJ z_Mno$?s36KluJDm$#9rE))|V`G}C+zV1m7$gTX z9JlPz7ab$(26Xey-QNsgFX>?~;UP*qKx$yS#5-RBP$NGOQPkXw)IR{OJ3CKrW#Ea! z{AA>tC~x^@Ja4cB%wOuGBMH1tTM!fn&(dMUsNvHpbDM zo!%782z%)nFX%6hwBC(T^P@e@1Aty@S3syqXjm-T>S`-tLu_xfat1-qA8o+R9xTS8 zi8A2%x6=l)-Co{n9=rcY3>%_`2JE*YCY>tjYPfp2k*<=0pW+_RmytlY`XI<5ro6iB zQuifjRNqW{WkCKVD2^Y*c6X_^2%IJSTZm{&)GAG2Gj2561@{{MiNGlvoPhv&$Ma>O z@fSbHN1|xKGz{bL>)vz@Ge(V>3t)uO0Fc@lpve%30V(fF*g204jJ+A4+QdRuBpMXv z@_gBE5hUsad&0wXc|klUcXk#awGGg&dzw-D8QSz6uy!@Jdsj1Ic7a|3#2X8;q~wo@ zalhB^jhn*DKyOc=;O&SZQ~sbe4aSZYJAjxhL29?hjCY`H)W(bU6`WFOULg4aq2dW- z(sCV^{&GiX9%2~GxxOOL9g8~H#(_?VCjeId&x_{1p=S6Z?jGklDRE}xKohuky?JUd zw%mA#<#eq~*d?_67C4h}R0~?VuM_|NxX=q?FVR>{q*X+JBEdNP^Jqf?T!ueWVv^86 zMdN!H(xTgzZ}L&kjck6b^Zxh_m_ptKX!Zy@JE@b8XG_;%vS(g%SJ!RJImHd9-jQ?p z@968L8O%ZJ!gll6TsI#m`7wA8OSjbyBuA$>-$;V8klSWJgbxDwA2`4Ot;^87#ATHv z?7WqCD(eQEB@4T7K{@MYA;-751|Cacbx*+6Q=1}A;xl`T;=S&r#mTN~ekTK3uBdsf zNHo!|Fb%$y3xV#au=8cbykIc`coSID>XsS46ydlx4Sf3FT~|C`SNEiDkIzvr zd^XS@=r->kkJ#qMmd9GjAQS2Umgh7*3@oho z*9ynuQ1w5XrLyF5xdZAMys;i5nQyKi7;Y}nSC21yE@C(JNko&aL%5+DbLs9y)g8|t zueS{TT{&n8at+4T82kL@y~BFI*hjHt&=j!tZiXTGm@ifOZ0zh?(A4>7;qS@A=$6-O z=E1|R^u6(Fg&M4xkNNqg5W>#;^CJyAP6CVEQ7id`urISavX~Wyfc%zMl7gCLodL|vw{_kxC0$M3 zE|c&ip?>Y#10}HxS+i9CH_rRUDk0<{-_N7;3}054|IA3_32PSBvdWI^h=b#s6%q8> z!%8d&klBhQ1W)WQ7c9gxL<2vu`tkGX+sP~3Ur*odMsj~FdTr&zD3$q_wY%)Y7P;xY zJ))SpA%)Hkb8FZ1b5>66H*~X7w^CbEm(|-a! z2ebU)gj(T7W5KRw7d$M{c`k0k&I`N!p@OS-GV|}c9dhn?o9rXYP-tC-dNCvep*HN;p`iU3@F>4 z6z`gnVQ(L-a0<#k_Kkh@spjvd@u;b2ed6W^U%wif00pVLpLO^`Fsj&u zf7cs25m(5sX-%qzE$y<+oL-$)hmR~8fGW~F1p--4P!X5$VSQbPfD5p=q!018Nm7^$ zmv?^JVd1yFz6Q&n7%N!9tHLxph$QN;$K!cTLi|i$Ak!uieRZmvv@RFVr3i)1yF+X7 zC^X2SWu>LYRKi=@i71F}0zbz0$D^-nsm7aW)`Oc9rFq+he~aSm?9cZ@jS6O_1$QQy zol13|lyB}>nvGvs?(F$$pT%gmi=R>b&T^6Zh$wL>Ad7V9Eis0zAMzLeQg_$XMv>)2 z@2J73!XL4+)+O*oJ)oy$?vP zpzH}HnEd>Eb&ld#Cv2^;mV9^Z?~6zloImN4PVtMADhzVOgyiDnb89wkv%l9la2@5O zxx7Ua%-oVmZR3^$?jCUQe;XYyrGSpf!Hzphsw{H0=949~?YzcZjWp}KN3P`$rv8K! zlI&JIH+pk7d&l!9+qj$7}eFHhbQBt291H>&5sM|D_n#Sm~9F40lian52sUT)G_FpX?f?lkenfO zXdYQ^ETewXM>l+h;|!PR&?`?-Qc`-x!!w$)YC*hSs34&izJMKYs8^Dx&++V8ex-3^ z3%Tk2Zi4j>M5A~|ENU;p!^3Y!C(5+*sJMg0(xWmL4MR7|9*d?m7oh$1!AL4kT=bH) zt&lhXj@Qp`#s5Z`h<<>jVy@7glOiySaJ=!J?1bSiJjT5F#;`XP=kJetCzp(cMFt(i zXu}Nlb)vysY3j)SezK4oT&5BjCy4X#AfluH37 zn|N}-lgK|A75=Zp;^-}3lNLOX_^G`4DLR@^%$!8v6{?+202#hUEd)zRc$ZK~(I56% zEkimYtyTWXU>d6L)A{E6eJ62R=JNS*Y&TwNq1f{=iniKtjVB|%K8kBFZbo2v zbUhN!vo5Mz^xe>U&xhltx$=QoVvOD%H|B%U_w1ZA4HFX;rR@|*G`mbpotUPqEpRVB zFHik{+-tdGyg<=dK)0`lfRaa8P=c3>Ej4OlrML`oi$J?<3lZy#@0MTpGJiO^aBlGF z3PnY}qw}5$&%=AP7F&YcXOV&>*_SocI_puVJfrx}T1h|H5J-N?OdTdqvR`~6Z#j+*@Ph)({>+;e8M38S z;#ahO(Y2@EvRvLv(}D$@{QUf%U5#l; z=tc?z!@c&pe|+XM0_=WKA|_<*_I4&&yJ+gFqv%dWy)B6f$kg#^M-JVeHu$#lASWhZ zj}46d`KX0Pyv>PnHc*L~r#n+cjY`3YHEO_|6=@4@!LMT(F7YM%uO!{(-LgY6pU#p4 zrW%d!)+{L{zTEr4V6C|PQlRe#3Cp#G4`wGq<4N5PSYJ8E2 z++_R&3JIw+*50aY)Y@`vL@MLCZ4PkmxdS`0dtsWARrs3p@q_xAU*Ro|I|{$GTeQz7 z_9s{o5jnYK5_|JW-Xx}ZzHM#9K!jlzVA@{i{)XZ5PXEe$cB)sHJZF;hp%{4fQ@M$a zWyq-{oF$Boikh)$Ng9E%!3fiOr?yO(^y4LWrl&ZsGyfW)P`qpL>(|SETZ5kkR&9Qz zOAadqWhE)+ zv^UpZHobpoMdgiT>uTB%#Sv2TF(;~cqH|OqzbyY?eCzK3hin>33(d z^~ZAfw&~{GZ*+V@#=|&hNp27(2?iVF6_Gr__VeSJabQ_IddLqj4jT5gpYNuIG>FlL zR`OQ^7Zv9c-8u2!X*9D%BccD;`n}3GPV(}lq$Rlkr3(rKqM`U~@@%4OHc0xl{p;?< zYQ?5t6gdit#67tf(RrM_ghfql!BQK0L28J>{lmq=_;ws^zQ-Wgxsnd3EMpghmfz(F z$%jRp`BoqN9K0?}HSRiAj^ls#La;{Rjb%)}Jp_P`t@RGXQ#xgAFk3@pNBuSq;!He2>JW zPcRD<3XOW^?8eGU?x5k1XD@gra9)A(1_uf-Gi?f^Dex4m`lNvDYZrS(f!M1NIpZNe z8lYI}BVP$n(GEm@%%N9@PQ`2Vb_CRL+%T(+cj zAw62LhU`-J*~GgmIhM!B|5`HLb%u|gzeEStqrC(cr#bW>FmXps>`2fzVRFEV=FYnv zGT&Nc0#|%L`t;g41@XpyZIsSF$_M4$V&FbRWAD@oBs*cHhN!vW-i&;2VkryAOEf## z&(dkJ`1o|IZd<9w$e*vYo$20KPkxX{rr#YuUkjtgemotCLazuygmN%F;Ky}&BB$_V zIuI4C#E5Q!_eSV(7xpSOB&N@Qohj8U?hcn*ViRvLp*>FU>b1*e0@H~>sK%E!xPu-d zxV+w|%?s8hW)#<>$;KmehMm{*_Ca;bT|(&BaZ9ZRinxkssp#{+8l_=%W2XJNZ_rZT zWIZr4Y~5v4g!ADEKkNY6L%0g8>=!%qa3W-llEPS92#8na= z?{w3rbr=VqM=zi$tBz6TMMO=-fUnCbhU+xN?;Gik|4ib zlJ;JTW31q#HyzUm-kZ1>Byyz8KV&e-_+KjAdYYmWp4}Z#L&$M6>8C1zF6s}v(1yIj({aro&REL!5M##hKJS3ZRQ|+Ob})dG>b&>y5%)eI zY7w13g~}2^!)z??Txxq?NjQjT`~(YNt(jWXbmvQW&Ul^NH79xhH8B_ByR8dV_}9cd zlhNJM*VH{jTq)YWhRHYzP$zn*w4~wj20bN(plbyh-`|%d7`K7vQh4}2bX4;=908P?OOiqc= zk~2WWeQFlDbcDC}y~R#88IxyQkYe{eTO!IY!?5IYpj(LB@X4P7|3Dj#p^E^fk}dq6 z;}&h{zt*~s#-y^3sOVV8B}r#~Rij372byz77-&)eM=ndHz_Re=a5W zjY=6MWR#$Wa;fC^hW)C6ASCe)8|FG2CI*!3! z(Ld##9C@@5kcIn;WJ`c}?CzFTy+p=CCYo+^zl({9W&SN^>4LqTD351T%f1Mkw5>eN zC{jS7)!F}bIL@`TnI*=GD@jD_(z(s=S%9dS$h`VW7}jC9VEr!yU#Nyvn}ZsOs#tP<48!(p)#+~a2w z9UVLp&5dmC3kyLX)WpYCHwhc~AyNmUQYH!=yR8WvBsIHCdkUpWU;mym`W|3kd5v3# zilxsDb;LdFpjGO4wT{1@uMoiAxp3LnC|D9FauxSww9(KyQZ|GY%iyV{hr#V_Z#6Pu z^2-S&pmQQ*0=AJaG6`c{jEfoJ$-Q-Dd@GTg2xspwB9Ep!WdIrt*%wSyUp{~STyCf{ z$k6M4`FAZjA)z4EOck#WXe#;!e;RKRoS~Tw7aFZUt2*8o?WZtBprA^;KrbQ$4quS9 zMMp>9XArPaiC(v@ycz-^FL2o0)uE(Zh2YuT{P=7`<$Wd z@{vo9vafi@R6e3taRGddx!@e?CWB=N{b{&ZA0;0>4S_)R5e0S#bKUYQ90vQoS1VZ5 zpea#dBFpsgp^l9z3;qM)lChd`Md-^YdEmtJ|+9J)vXu zH^o~65{|t8qi+#4o)X@k849*7MJ7&}t@C`our1vb-=O~Q4hXp;EXNXp`I>+A=_C0b zbiCNmrWTA7a(@25J|nub0>Z`p!e3FsF(7526h8+7!{+tHpy`+Oo1#CRbJcPtztO!5 z*u3qlUI^DKdwfs+^ebb)czGUle=};GxfQ!HC+o2&dD|P~69? zMV`~%aYKbn>eiYfB|ErS?ZPCZ`JG+t6PLG`bM9>x!?FPn8$Kt+g+-9NncBNKq}eJ8If@@il(Yp;*}X@@t*%=&I7a6K+cJ9$^_M z)i`#werlaEc>j*Kc4G6G#VC$Z!-?@i02*MgJ)5;_bHbdyCxOnu|i9!~y+~auJG` zdms>{P@vExHbAmcNci2E68egO;GmY+u+b?e&qc1GiZYc#3Y=&LP>dAevXbg*)3j%e zwKKZEU*A4$JC7SJ`BDAg7slx7^a0v21?|LT*EjgtZ{Y~QII(|Mr>t?^Bx+6V$A|ZN ze&I-J=+<61;ySlM0I~rap?cw`)9Ek?+xoux%w3qeROynx^7DOE-Hu}d@my!A`610= z5H*yo&$_wfG4x;o-=jwa zYccP_RElL;_RR_1LbHnAsU0%34ii-i+Hg?ezaz0sXEg#p?6@zv%4yt0cDv(?ZT7uy zBdb}PY**P2VeHN3Br!9Z%C0i&xk|FcDY-=*(UmX!wePH;6iw_yF?Z)Fl$nBA`%T;q z2j5Cr8e7D!f1iiaO$aZalkwpQt$LR7=l0e+D=dcGLx=m|0xHrB0d?rELDR#4sgCFM z2bFgvJ<8PDd{WC2S6yHKn*%0-(jSM~j;^N3xv~w9jFQ~oFy@NI4cNTa=;b8XN;}j? zs{)n}WbG(APl@G(1oLG^%uB4mJElg9-cOVJURQH9rJXFJ)afC)GrGm8b3O`ln^x9B zEZ@!8_P*VSzu(T$K*E)yXN<8#8wQ8hqw|Aw6;>NLp2WnvN%a9dOiu{#g-x$_?Dw{OnOw%H7b06lajDV=v7*u88fZo14#C_=s^6+xy&< zAGRD+F8_J(S|L2r*T8$xBOVezV$1Dmw>iGpvxq-%IaK6ScP zHa`M(4g7MfKK8=?Q%-Ei3+B=MNl(Wwc;-T0Ss>%5 zO{j7{IMV{)+)Fxej@LNldM<<0c;LU6?B;K4m1*mnt~=BwTk^yyB)v3oX`V##VktZv zAWIJ6!9Fdy=u#4u51SSP)+Z8X_wUS$BG~pSY3G()j8{R{ogYK4u5VD$!;x9kdgYVYgB-m z79B--0dkIL>e*Z{<3gLPqrTXp{>#-S_hkonkHXvsw(UzKww{57m5J`_qd}M9^eOgG z9Btn(Zf&~!ZsJKtseWZa=75rH{BeWBHI4V|H|pTcTvL_%(Y!5)hb8P@ku|#U)8s zOu-Y`;;N-maql^PY$Cs*n{+X_%Zj^s5&S0D_D6FPPC%c}6&oS#M7XLe;JXYyfMf^{ zafdTpG{z?wKQzg{Hg1*qIp#{H&(v1kK*~^u?F)enr|T}2-P4or?DFMoHMK$x^*h%|2W znN3J2`Ty#E$IiWl7Tr)zBg^9>R7Z>3a7Hvv!{}t=3xBztfUVv|`B1s)eYbM!nyBHp z&RS@{9JxI?mwn5gb9HDrKLyOkGM}YGg6{&qXIk$08THrrH8;}&u0iLFQBPS}kA>v$ zaTpgSDZlIRu1do$SYJ5>)bM_*utg&#jqEDzkY41o>pTT7X==OnFwxcZa3k9K_c|#S z#R+rT=~q0Qw)0dR(M7_9mI!VhQ4IS=*{k2ti2114O(!%*?P~c*jw=Xtpp;6bOiim% zKy~ZB!L#@-hxu_mtC7Z)ViReS)TKd@wEV+z)Fh;Nfs&Rs1B+1X@X2^CyTLKP(f-o2sF93Lw$3zYXo+kE$hl#;zH*q<1RlEZlfR;y4Oxw zJsO-QrHL#!?d~*DdNI1*OYgEqM@nr&_p->qr-MERD`h-6;N#+wi+g$>8}aA-2d<5X z_BiHW?GQw=P?p>OOjC~ej_3Vwki8uKmYFMgrb6%h@`0*?LSNhru<5RK1Ma{XY^*DS zFQy`8xe6pum4Szo=;wlAUWgjJ7()7vLDct6#)gIGT@twig-+3?zL&EvUxuLLlxl2T zQ#eeWei9`&y-eO;(ut0I(Vs z9UVOvE8#{2ZDj{|=|;W|{?$;^s*&B~gf~X)E&C>11Nc*TP{NtKsNWwszl9X$o*Xy} zoib-o&7Xgaj+E@$;%f^pNj;}YHd;WKxvD@GUky5*8-`$wzU@>7OS*m__thj@v|Gze zt_7epQxwvIZ%x(fiReLdGntRJTNOQP}g;HkZGdulYgwM7zL` zmFSl)MS2!@87MG7fpTZt%uB}iKVwVVC8Pg;Kd@h->ji-rd`?{kBsFnCopEhngKD=c zjzvep_>E^0eigSS3ptk3Lf?L!)t&wQ#ep%V>O|fSi*lw4>s4y19^BNORo+Q6f7mGn z!D5C3}DbWK0zd)}?0(nCcOKx_-S{%1W zbMdTAWoX!k^hcCekc*h}={Shcwp+Dv7{Mdvj&(}yKIgF?*SM3olNW7_Pg>Jyx{fts zFV0_)dBj${u8-*>ta$aJCELT(A>2o8zHDnVP`50yRfL7le|*<{bXb|WlieVu#axIt$z!$2W`~L!#cBBpXuKS@Xrs?hMp635Y%Gn6>;q&INFDb$g-bV zpXkJI^!)*hch_h4ZIaFWMl_74`@oeUq5v<=f^HomCcJzH!4UnVsTpS$10jO=UG7O5 z#CY>*V~EBc`GE9FF~k_U=sM}TtPNyoy|$j7OyRy1?DVQ4$&WwEEjVLLx5Y^%K0LPP zBYYdv8FTUctmo=z!LGkrp$5AK`!H{LM!fI9N1Cp)pb;x|c_Skavp@BV%&(v=+}d}F zo~}Q@>@`83_yVfs(p4xPwlLo5dZDdmsmhCthmFm+cWAS_6w@O zMp&TNi4+YKDVpt@@>+o4g})W$X+J6_t3Wep!=~-Xa(QWq8j-_%3ySMwF%TYL>WX;p zj!U%kz6-qlywb@4y4M^Jc2`(AjI55RaD07c~rm4mQ3k`m6>R zP2NPj{!t6aq}bBv#mmSiZgdT_?$K!#WG^vTN(BRNj-n>~NtBlipbutng-7u}3r)VU z#~Px5Ub~QOZ;?;TSx?h1ULbKNK~><9{(}&g;@BltJ0vK!G=+CG>cS9Te;pV6kyo3p z^W)Kuj)nq>TK*h!2Rm!ioJ1pzwa%j^T<=L$2B+}Fof?3n%Z#o^7URUS#x3o>qn z%HbaViP^Oe#B9Ng9dunJ)*rCfUnZUU5-rWgEFw3nRck`2$1>~CS{buyL1`=^h@O|q z35I>RPOn|qi68+e;$YI(HP)ixKSY23AGKV2IMeU@-yG)9hAAv#l1h?H8Cy+?nm*K0 zgmT)_K@K^y@L}dyIrT|ekwaq2X&s5mPOO7aa;Pv&PWiCqv{?3g*Y~gA_5J&My|3%} zmGQl1H);{nn1#FM( zqvtbHEO_j5a;pD>a1b*3TzqLTbY^B;XL3J`1RFY(7J_`n4S1p-jMHr{wo9~8p2J^V zaZV~tDTuJ7>agO&V5ecEa&R^9ya=Hlebt=3VxeV}8|2etPMD2^f9K~!s-_T=UVjt4 zM{s@`G`}eFhsTD-wG)#Xpkr49HNLM?21dJVh*ag(lTT2zY}xgqVhSRMTe$P$zDEM~ zaeS#cd(tWNC+?e}Cr`N4HBr}XBJ7?0r!YZf#+s?C&-VITg?It<-6?cgv%j-mE;=8d zGo}j>#d<@I$a~0RCE_N?uZ8<<;_q)KL4(jLb}yQEb3b6xM(FJw$=U=ntjZpzR~}r@ zD>rLSC#VP1BLNPYnP;rJ_zo!B1Hc4CRKIfBXO$TO0$eD+=kXP=)O$en~%TUG(Z{v zQg=-XkNo_2RN|e!dg{7RT!5o~$Er7EZ2c?MK?rDvt>&OSj<;T7 z`6ipT!>e7Df^s7_6syGCx=b0ob?HPq0gb#UCRvh}-_9(RtSTLs){;p~VZnmR8^H|KNk)@gea zmd>j8?@EaGO)>Jg-$Y8h2gmCvk=nsA+XeT}weio)_B9=&T`FpE-CC|&_PmnSnQS(Z za;bGgpB1Zp0TgYup<)MHubOM67xnD9A8=mb$F18LFPXL>zf=*9A)Cm zTg%pL_OMg=3XfD$3IiD&fMUpI0IxW^)W4-tnl13Tqh=9gx}6-@o<3_FhR7e*&@RdC z5;vh7me;c%J9FH_6rWwP{VvGvoa2XZvq{#(V2$m1`RSjw_9x|#Y@iMgRekLZptHRA zOnZgxWdi`8C>F*LlZ)5l!J83{`u1;Z^6R$@^O?H;@^0Xe%?45YS}{R1fE|cJ zT$9MP{T1n_YS&(vhGMT8H;?zVlOHlFgbK^fv{Kr;utv-;>AOyMcxZr$Bf3ManjQ26 z`*`4?KKkgJm+5c6O1gD~dm#~4d&Wc_wT-IY=#IGtCF_xmhdGC<3C3-Y4c3P5Oqfj< zbPpPj#GH>m`A8=Yy=@v?4WDkEEJ0|;1zdc4At|Akt=S0opZda+*ko^h9iWnJOb|WI zcsbmcaqx4FHA~XnZJ3}E@kK9!vfcVlG2+QUgO1TI;;mCzKJ>1n-UE9RY@zN`%06jN z#>!~#l`+Uo`gAqeP@jv7Xx%n+2QswLflM^m)|UoyIdZuR6-9z)f++}!MBLYdT zBpq86;!w?v2X5O&CuY&+0_k(wqos+-IAgJu>sQxEZlNrLBHd9p;_^e_W7vsvZSe4a5o5FT;WS?q8T z5c4~o`x1Yi7i?@B#(h{MRI_%WJAx~p#*vBYtpses>_N;Cz&17$b#EqG)@-$$2N9t1 zSn86x5k_~Gdr;KWvhpNmTkMB%Izz`(BUG1lvjiHEUox1_Dtr_-OJCU+K6-91%9ktl zS`b=CDAmH7T-JmN#H&A+wL>OmT5W}S^_Sd+$|>zD-JqiD63*9K&*aD2_D`D; zv-{il!m0e&){s`^SBjS=edv$%-@JIpj}Ttiw#Yr+Pm3*3j7Ui9^i<_0S29d@5k!X( zsMG}61yFkNvO-&g`t2~pS;Q*Uyew0*5e;eKG1zN0{FlK@wXtgUt!I$2YJ1eFfApT8 z%yHOF?eJQKh&ssaa+^Fs5tNJTvbd&b2F-lP%`0=)za8YuaA_UIfyJOAeC$l8CKo8U zZ!WVBD~IAbXKi6aA{WzRNu}ztTS|;2j>T%3DA)kna!mOrv(C_bLd^(YsQu3jbAOFN=K<$(@ZINcQy( zw(_b~(Fc&)P8exHdGWMISUdFpcY>jlCO1pN`SH!nGlxYua#E@J}sq z;g3-{^G=})jtMHY)D`D_nSaB_f@&{1|G3R8$d`=P!^ZD2$R>+^8|?ydQ1A0-THQXK zwwJzrzblHW0?p#ZRdBI5yZrMcfmu3@PaSsN@tb?~=M0TXqH3;4Xg}X$)v7*(Stiq( zN8ag#)&_D4nA$Jl{dQjr*zP4oTO4-LFo3PA*o~|e-<3C`1A=hO(zV@`3s+O1Ka_6jQyISi4HDk#|J~8OGFWvoZh7wgSX4rxU*K-!xYbILG z%=g-$rO(pf@paY-o^Usyuww;4x3_e?p1Q+Km>)KT4N(F(GrXVM%`n*lZ}fTV-|_5M zSiw(9LIJ&jD2kZzaLb_>HR$AQNL$@I_ocaZI#6!TXE@6K$4-BH$<2TH8g?-pk=DJZ zZAi8|xg=ldH|YSJVS=Zn?^sD0Up*hC(`FI}t-?@+Q>XGYixtfGyE$nVOY{5VS_VUd zcz37y5rWYE5vaq{BOIjxS?kB3b$9kS2ga04v=K6b9S4>DGHT^4)|Nh5uhmv}TEUDk$9oT#8W~z+PU8<2wvB@|0_blxxHIdvymS-JnIK zDPrG-KHTBdzJWmzhN0fv>WSvqgy4J1MkF1-vT<%Qjk`(7_l$`(t!#}2im`HFWVfv8 zq2|~mG5%J!ixR!Wj9xw>-nD*ht$XH=dFPzihM*`@OZlr?90?AFBk<_oUK>ndV!#Hvdqd zeA>mPcsB`~N0gPH-Bwurfur5ZuSy>=Zd}puW*;7rN5pBBWX>1Xs&Af3bwN?V8{z3b=!xNx<2l z=1w~E(80~x$#W;hbrB0l(U*Rnt8Dz0dkF2T7czw(%8R!VF}3OlY`&upi>Hwm0+&L# zVo(jQ{F&TiBdKkK#_S_Ip7D71_c1Wu#EhNqwLA57UGc^Vn^@xLYz z!G}lAUdiWQypoZ%FLE1swIh?Ne=!4V>8FrZo6jNR5tEN}GTFJCX&SP8Vua4^Lt9>) zE4uO>vxJjwDkF4vbcdoe!^Vo$b_^>9j%Qn{OdRBf`MJd8`KN1VIDNaJZ@>udN${*8 zL@zVWH#_)rM~^UyCymnZQyzUFeP{|yhA7rMZig}I9J^S?QJ<8%6yKynjww%ef+buf ziHA*=G&Tq{Q)K#yEb075{|wEa2ZtI*0WPU&jw_oOr>0Gkbt0=MMR~!UV{1gB+(Qg=2$F+B0 zSiMrfx>1fzl3umv1�xiu-X9qNj~_jsi@CA2l^wSF+ZZEl>V5{&S2a0pUj4YP2n!>2cNKO6|KRm+})Vp^2Epq2)=V!VgP= zUJZaS^|!)2!PG>S)^<;j=)+IU`J_}YoBo2gJZ%qyX18k9S(z3hv5-~Q)Q8qyI%l+P zf*=z(OoZEJnri(rwv=c5x%;N&jeW-i8JbF@#5CcrZa2&-w$8~k0U!M(iL8yJwJ>r? zaZ`s{4Buk;^sfIr&s(v_hSN26shiD_rjFX3(nT3w2%+=V9k1Dfs9Y_Ew+>puOfO|XBGEZebR#1oJ^#i7ndwPHY|`zhIj4H8#yxh#p&vZ;tF4C zNaONrhhXCf;8Z&3MJB^sBb|0e%1*9{z1}epER}g@FCB5eX1rsHH2juFU|CQ9mkaTD zM1DkW{X|mat1IDkjk{ScX|Maoz<^?*t1_Evqv`7g!V8oyOc` z1i9Y9#i{(G=j{7)X8Diq(59h~4Dj1QYgl!zDr4>4FeMg0hQrA4{Ad8_qeAANC|i%~#Elxo5ji zgmF8WJ_}Ha_{FulZ(Q)|7whRJc5zVnFSWkp4Y38#hH9}>^v>4j;*+oSkxQqM<{!+1 zv8J;9xU+$wDo-pYfuWrGj5?}FP?9CzWf&&JvarP4f z@y&KG924z2_To5~z!B)+M}Y=_&;7q#6#2HYEzT7Tq+6re8Q2L+EFTr271)Zqo52K2 zqOL4U zc(g}xDSvbCBP!c>?WDUe$yXtx#m6Z)FrsUpvsVdNwOuX8zI#`Q+5*SZm>|Ho@!}K~%I35; zDmV35zZD)oHq1SqO3&TYA7U_M;<&!@L3lNK-^oQ*INjN*L}a32dGS^9$2sna+c`Ul zmY~@E(UNKGpegCHuj+1u3oekzztw;V*?P4$b~Y}Wd327YyG`rAjaL^J$5@@>;kcs7 z+w!kF4->8?G3-n=2!)B?^Hm#0=p63Cwh7OPk4SsHuNUjMw0|gPMV&Xe-PI~JVbR=n zW_u>RZwGxmPidPtwy{ijM7yCO?Hw*gio3z()HQ@3%w6w1A5h#)Z8Yutt*CoH6wssK zhBhC(9I8|63ppuyJgCDs9s@3(vER}AwgZ{L=c-a~z?Lcft7Qn&-*oh9Xe>PC%r)lG zpk==w&Qcj*{jO{#dH3E2bS-@1<1^e_RkJaLcB7NImHY2zkyUjc$0#8g9t+@#l@u`C zA!)@qHfzI)J&IfkcdABM9Wx){A>PQWU2*19`Z+Z@W~vIv*K_ple-@mxWM`63BIbNCYsX_MkmGxk4Rddt$tV< z=J^3LJ1U`Er4r5ab>s!Ik_UIxp_6{Em3tj$&1PwV8y|Htjp66x&LSlr%H{pahigY` z|KI`l|3_CaLoyA}kzOBW2a|-<{)C(}JdheUe+&WzzeYnFpz^$0-K9$05Z>>TG*hA; zPAq!4nvd!lG;Z6^5F5<7%mrVTG*^6QjnOzm*doh}5dX zcB%&^dZ73wQZz?Um6*pVm6T{}Na2E4-}?YYVYV>lx2de1Jq{=eiCiIJF1b)F*7vR>2%a-H5e|WPWBRU+RW(uEq9umV~zG|@9q zXNSzj`awvhXG(?DWPUMiTY43JCd3g~qStZ;JH$L~S4lef2`R0fySOqaQ*oSqI-Ntt zweIzd`hZ}y*-Uo}TW-Bhe$`RyP41BP&ZYvouolQ)AH5x@-T59c>r_r%`)+7~Cto3v zN8nNxCaKpT>1ZBAI8>^4<^pKZOz8s_-R^F6tQ9ktIBt=O@|$8ZZn0jn8CiPjCgY>$ zA>_4x4KtysTTZ+5`~pvh4Or7*-=Ri`DbzW$)DUERtS@J-R@iPL@zVtE<)H08ERk z+)2xIqvT*qV1>8BAq4F8ymg!HH4Eh>t6q;C9sp(1lQq#Wml9HF?~DaJ<{}$+mO=2s z2d||rbAjQ;D8eqQuhbT*U#?FJg)}dVIYc^@6Fcn~_`x%f-h}DnAH8Q3!=Op{7|)NC z=zFvL=ab`BM@1&d2aOXgqUJLB8Ix+F4}?Ma#so`){x@w(uG(+L_A|hSoi($f*Tu`# zI)_B3wop@AAIRn8Gr6lbc|%&Tott&O8z5)2fp|^Nz;=*daSyp%;U|%1k=(Y{p=17% z&2snrtS^*q%#%Kz{HZuvX}-~JF|x{BEV)Y`tQ~<7)^% zEWVcK)mfeTCG#Yo@2mM?-1?P>l;+hp_l36Rj+X1<#Mc#tf2hO6aGfvazDeG??-BzqiGNzMf_4BRKue=e442_@m*dXC4pO7~#92$& z0qLE+=t$qn76hAs_V)R*WK_g0(_Ynk^AFz?W=!h8=WN07uh6cT021Qi?5@3Nz3~Pl z{Hyou$-754_a<)+Uqd?kgP>auP}5>xh@)<9PYE9Q6IdHEF6mY6K9*3999n>NF8GD? zm8AVF&OpN-b+By1`B&)9ldI1aZj>-La%Y@)*TTaAo9a?YJoz`pc7mW{x?5!iYM-jD z7Nt&r@~Si&n$yQ^MBZP3%QtMs3l}hFlp^LFds3K=8Xj!Pn-g(#nE@05)eSIg_|r4- zx8}sy0TYk`CH+mFsjhp<)SxB;{p6@KG{bbxf4*3XS9?{3=ltdJJ9xi>fw#`3lmtfr zyUl4g+LKERQG*XXcqop)gu_kKKTf)t0{Ec;hnHVS_aE5;QCNfUkVyY{lfGFK;I<)EYA?3JLLDpz!5G0A~Xc-umhOfe$9y^ zKo=Y~mNz&4lc8j2HplJG4`%8pdo_Qs5$sD}2|umT1&jUZFSz3Zp6kx0)zN47v2mVb z=AHMn?Y8#&Rst^05Eu&6Bj^c$`$_s%ctm4UJ&Gm%+&z!XcG^!H4Iy6 z_K^JYgNwof6uXp|8-Ar4f>m$q5;jq2C8+WS{*LxE|KuBYDgWbjgvuL{YVy#fdxQtX z8X>#;&WrmptDi6JbG^?s)jr)ktk0*ZC*;F#N>clJT&|IU4WxouS|Z%aEC+H8ufz*% z$KlNg$>hkWCG4}4CT3T_MQ@;+j^AzyOcPr1w}`U^;CV<=1{{HU()9JMJ`S}D1A4yD z744ph9d5ft7^>kCLwJ(4G>1QUanh8TIOsGoR0q%2TJlddhgolLne(K z_cFqrXsqSgWgND~Nd0-q=j5Q)8xg=>-$Uk#u-n4~e*0t6V~W6m6kYS1>T+5YJ;HhX zyEmIhQ3u*RBq$*H%sSyt5!fMq&SkB*+s4fl>ariZs;Spt;0u|{!*0)yFEl`wWFSh7 zSt9%&VD87lk`K<)1DtXhbok`?OUIG~K!()#7D0ruol;3^Q!HE$?E0@fLZc2#8nt7J zYzVJ2yzgTSS0%?xR)R|6$7iIgpJ(P8z=-uye2p7a|BXgdev|m`(q~;0$R9)YH9V$L zmuh0jFp6UfBY~qg4agQfq`^}2Qp52P=5b2ST3yxCeYO&J{z0gpdZbRu8>53sExiA-}^+-`ejw-ZClM3FS>c02$1-}53X7_@-uNDSCY3t-Wf};HivkJ4g1x93q z+8VA{bmOS&Q+A1=8WC?R`k;2`ydiB&RohW|c(J0i)5W_P?m>(OCKKU(det#(9CS;K^I)i@c>F})u(o$C&nBe!ZV)mF>>i>^I2Py zIQ2=iyIF@QT_krtgPsPjK98e?lOZjX4-xRiJCd&2smXEioe3nOJhS?wuVmz@`tDNx zd+>M3J7ppT?zz{9lM?s$OgcRJp9MfTMETI(f}LV3Z-#xOpu%uQ9V>hp>jm|4~F3`A^JG_!Wt*d)Jm z!twh_iGHr+yq%n3&KdZj5ke`3mM+ln`QD|+mMxeBltnCL4<#IV-O+oG)yuQDw=wx_`{#;_sXor(9y zGYqpu=wl;0h>gDDk}!G}t=N|=0cxU(DPd+T3qu#1n!#bZdLmdLP)|t%IJd8qW0T!0 z9jwix51Uj)Tg_(JT>N&7X{fd9F&-D${05FAg%53|z9d7zp=RiUP_h0d%Kc(}1U3j% zqJQA{(Mhj+)?&8oD>SjJ@OXx5wrKP?%^vcjL^-ppaoq068{D!_3X?~e9F-nHHI&xx zZW3@G+~(%v|EDzkhu9Gg2!u+kT7Fjxt+tzm&{K>5j|zbQ>{Oz=rJp@hj*qSsc?&<9 z7M;1B`lD{*raqioEbZU5hXQg_8vt;}=SNeV?G@>($< zWQ@5MNd5J~%9pdrw1+_htstxAvP05#5)=sv7_RkZMAzc9{9X{@-%ooi9}DP$#{@x! z-4AYw4-$YxSOV^>j1?$1k1;do6+ZPTR*atV!~cwRs;6(hY|teKDt=302eJd<-$ujH ztv9<9xvJA8LeTq63lVO}+?yrCS;OSwrz_(xMN>_b-&TXR!>J{^TrnvD0D3^a1|ziv#TvG8;n(N7NCq401bl)eOmxATT7F5ob+_HN4Tff(Mqu^; zF6Ghpml6;$9M{ypT0`zpwEPRBqoY*~8R_Zi@7{MK-`GN(DGmuNZyA_iET0%(vxa74 z`rtSr5^j4h%{{+n7pm4RfIGtnR{T`$>Y2n=PcLV-|A|fH;@a9;>rsV||1-4y8E392 zp)g1o#AZx_qCaBw`jlSy<@BRuL57#sn>J3AFsP%?$cg$^4o20CpJPQCA^=lWfq#_h z-iIC^zi(V+rL_3G1GB%{-T}f6E!%ZJ{qBBt%o`IEsb(-5IvYjWT3%jmJ|+rZXWAkLmwvum{0u-n64b$XIasd& zn@PtTCIU@e8JF|DxJvAEqa_3if)X5oFw~tRg4LvN!uCVgRye!c)79iYC+9LMou8tX z=z=Bmu}M%E-BtrWxTQ#eX~;1&4%)DQi7!U({MGWPCuOK7jKvc7q26 z)!{U9OCYs@F3AwO67W{TDE)HZ*7IC|YBrjO%C246uW27~{l$5*B>I*F<@Q1V)Ez-! zDFEm%49lnXTumg;)NJ^xPw;5zCTwBsIq}gK-?%#f73)I*7%q-LoWj3s*tO}r(0zfk zwyva3W8ZXH&M=N={fHlG6cLU^Cw*s{7+|ZOlDFKN?k>?;Tzeg}9xwMNZy07OeX)93 z{5c6~;~y#tniNBnvDj z;)|yxst%PRnId`VR_98?{QmzjNd^@b{?hx+er^~vMyfHF%VBC$Ce;6793Y& zeDhmFLm2^iUdSNzScMtsJ-SdvJsr~PT?j^-Wsh7I$5mH%tirbc>s5C3kmMH3KI{G3 zS38;W`l_lu&Sa@Tz$7dH54JniS5eU*rLMnt8Od!Jxw9pABv7ns30k9-LdkIg&I&N1 za7kYSVEAq{O|t&o-kI7JU=j$MPUiKF)8`t)fM-}By5Izg4!D=+>g5<~+5di?^U$5% ztMr`lDA?BsnRAX78<*4vf(1~nUPANX|Ha~z)8m3brtCy?f#qpq1-{5g+a#dUMg(Ai zo;A<~x9!60%>Fk<2NX{S3SKtUrvxBY;F9}4QTl)K&F)d*3&i!=GN5zthIC0rCq;%Y z(Dq^b0~`>U$^cEe;0V@wNl-A)Yp>oCeiQnL(|Y>PE1J`w`|}h7C)q0mC{V_;%tjT7 zEoMJbH%m~-3lbD1S)2cj#C15GV+&kE4~ z%8XvPUA9RkdFFhwAS7G9*m@Ex2PP5KAcaBn%c1xdAX)9JR}BxZ25Mh^Gxkb5|LpsW z)UP|>g-lUPd4&Ox{Llx(96+(Igm$3cr+2MEwd47lPwmoZ$6%0B;0SWI1!ZYL z8VQzgBRJ270rcbnEXuV_$35XbjzISry-0;=dzVv8YE21no|r>B9=7S7ICY#ODW!z7 zThjOvEeMJd^xAwR(g)h6j{PmSNP;R1YM}=NEw}WFf!YEF{F^-Zu?-rY#PgH+2n+Fi z6{5E_j>B;rpdnId19~@+xM_-SK`CJ6+S`l-6}bHJ`XKWf zOZ-+9fF7RG_6mYL7=gazj}3EZ^{Z8_KKbVMG;ZlU1zpUS(ipUXnn!N(KVgX{<5Vub z0b6boSYUv)i)Y^M^?M3joZVn|vq-&f>7PBEnAW|rPombsHjxf=!R71vhQQwPNhzm% z$MT83ozeU9cFma6SQem~0}bQ@BPjvvThZ@*!;aK8Vb@D5sZ-J_g1chRONg*$!&Ef24 zAV&jOkb!e3--z45VkZ^(S%K=ZX z^V-?}ELTdpZgR1^ZkHq|08j!!bNJCK1g-xJ&~kJCMjLp~V7Kz0qcjo#s&@Z6 zFod$yA^@QM|6NsEt=nI`uYV6;XCghYqM)WHrvxP5dH4(MQoc8hzXi`a*{*8Tea*8< z84pXbb^zB4Bf?fimFPV%8n(0tIuo+Vg_hktLNC<#*m6v9m9;3b4nnc-e4;UXo^;ZD zv*KH1DHvr${OCh2=0>`;d-c5K*8Vdx69P!=!j|5J;Cl&Iy)@k@I6|5MFUI8 z@5;EeaB9j|o-Yd?W7YFrE4&u?Afcdqdv)o*Ak6+Tsu{ulK{`2G$nyS9hDhQu@Ar{5 zF|*a4MXNfokL>gmasyGToZDDbjC_v1 zy62=#~|$-*SxZot|3=UPlLB=h{<)wRmj)x$Y-Eb^eVisLJPd94potm5M6$nWj?A0YnKg zQ?M7EA9r3V`UZziUGE}2{g3Z|EN9gUGd_IAzpV5rCdR27o3U4m?bCDqQI!Aba^%iy z3pUp37%Z6)7+w!Ng4Isu?vnBusl|lwkLQFq)1KX4mQB8g!E+4QA|=!eXJ3@w_4C$^ z@b`ZmE!C7mE3K-qj86QWCqgJxS2;|S%gt%PR1HQayIYEmx+iAOinbYx{k^t%8pW0a zsUYgh3Tzb9pVjNM2A6H07iMqf6`it4(obgoBN>S;n>CG)5U)X?3i+!vf!anCl?KO? zf0jlp6A|UuxRV}I6_N=%iDMy8bGiozyTR4W{x`u~Dj_e#q2?0GKGnbKO`%BkL$G}Q zL2G%M_=o#+?4Ink{ex>YO@sOrGarJD4_zO&u03WQV(z%P<~6qYm|d*NIi44(wH~DY zrc-jPfy=LGWIu1UChu)T=1J57ypuqQWy;8?=F2tW#{36De=h%9wG(+Eq;oobe&rJx zpU`vlYfpvpsvQa;A)z07!fI*93d%)FPJVQ=9PckvKV$d%6PB4g<3D`vqRv{s)5Rx; zZ)Wbc^rx7$(isamauHqa>X>e)bnv2KhQTAhk}PDr3m=ZTRIoi=X=Y7osa{ zraxv{TOIo6Lu$WVuvBm$l3kE0-C-&vwzMY#23ySFQ5@v`pxxtS3i{aq{}QXddy;}) zP|NQX^`RsA6XYu`LTAE8R{8u$(_Nv#S+ThGlZe^0R+np+%ct2imo+GjADWcGl3m^p z|0WEbN?b3I`UPqRVHAH@srwZ(tPPMGQuuD%SY|{=lSsH`qz$E}Z}bX_P>lEW)Xy2# zAEmjaNtyX${n|7D#HspF?X~i^nM)Y5)Inib7n-#ZBa(hZzM$t9SZ=_RoRp+xW|qa% z+j)OLDaN;q1W9_mRQ`$suBWB-%%slQi=*+@o3!JS>CY@PI<59dNlCA=OLdE7c}#IV z1ZYKCs|km7U%K*nDB}D+!GC1P(RvU*^xAKMDZ?9c<@KAk!F5-Qm#9nRFDsZxnf#DS zb%L0(`6c1`Cy5z2%hl!S%7@3qcNBPY34Dog&t{%Jcid%5c<{E;NkDy0CLwDuU}C6X z?j<5MLuz^`X^!iu_AA@Bo(&alSdp#eb?wRe=7~*EFznUD zOGxeH*4L*djq|k8>4q}cd*p==iA(LU%E@K_*FDc0sogWdALHXEY1SkjbPDQsrAY&` z8hYZ}vDV0N_WcMvf>|S?1`gVvl_4=z_m0kZ*e6OixT&@ev#d#d*^gT|6&{}{6ZVTL z1xHDG=2!=uYo%d*u4rXOgfq{n{fX)-!t*&N#`+sS9)drOudQVepUG;ZxJ@$1psMJU zLTAT_-;)7W=KZAJ@&oxqfhbnw!Yk|{s+Z}b&^ev;)=>$^A*#wj!4QD57g3LpSXVo+eqx+j#ey!FcQ%-ObZrX(6AVdd75>U-r+<|A z-t;NP(_+DOavWRO_fwZ)2c1se-xnMXGvOi^zHI^BYCDSPPc0QpqKj)su}7w>_!I8IBsWntyc2O zfwd#*Nyf{qO*D*)n_HE)Eqy<#p=31vkA!5_^^oO@K}MHmEI0lCrhNtT3VKn;sjban z)2-?*)A6UvElZXI^dIhN2nB2%s3I+7a898?BUv|Vak?Y-Ua%Ih*1Z$14 z(XUiD3L+YoJu*}wzEhqv`{xf4xRBmk8*|_<3qgc`;SKuJ;Of#7B%VBxL2FjP#@!-B(dfMN+QvRtsn>?t@gs2vNHQr&L!2v+im;5MrTLOAbVNq z5hv_uGlkw0ZmbEb)+jf?_AFht&Dnn?bE0B6x^7a7K(J5NRJ+Ap^%r>J$*kmOJ))!j z9zWHyiMf!?ytvXMlfu|@xEEpS8>RMdko&xBiIQI5k5G&^1v+2#IN!dk{CWvKZUgrC z@yo`#I)u**$qOj!zt&{IoBO#q;?;YkAbo-b+dfRIWaThY;RW0GZWktb#7!iS~up!NqdRV z{3I_bDbMg;<3Nb?rJvYPEUA8oR;|Q3!%LRLt*80|ei2IA!;fN!xf+mp(#m{8Y3e(2 z_bXcfi^E~u(g>T(MipVMO(MnD9cK!Q!_(0{6<4`aljmhFD|q4)arKkf#d4t#++&uu z_waa%cBOApqBe&aPr~0BZwtOP z>QKSId1~=CKTclpHf+5%4!gUxjYZ>$nv5hnmmsiCU3neGyum?XQ+?w~34wA{w-~VO zYwnNzNZN>!09+sppoDx$(wSJGpAJ|*A9&$+QRBLv2)?~lu^N_hdx}kJ)mN$i5a9~b zh)cJ9v&cXG@UvedxawBBwMQ&DYQb&GA`tY}udE8=TLb3Kq7mBRrARN9sTXHzpEr3> zqvd5Pi-b;=9mb3dt+*#_<%_jBDZ4~OO!K97fc1kZ1t(tpwDO%w0<*;Cai;F9msnq4 zW()GRzU{S2!S)kkHSplU?lMJNla|@iE@6co1O+-&RCoj;CeR)l0=K(Htx24A9Jd6B z1e&4RzGi82$T$W8memHdF6$|tnuQ`S^m~k069@gmRK!m8djR2bRr4s;coi~uDMDjS zXVX=k!NpN*VznIq6d964*Yl~|2}_D0MuxhROp{asvnUbIl+RL5nS^s$=mf0}#CmFl zFWF!ZSB@Z|h_oJN1Q6F&ukva7C3gR8^ zhplav2JNyF^ zA%o@XKX-^dMPZ6UX3jGPsgKk5P5+-7`!e;FF*mqJr`R`g_t3N>D z7J%vw4OyFp4!fBB9^t*VyC_Q|KH)QdredU%xfgZ!Y+e>3sih{c_75`kxBOk|rv3Yk z4*VjpYMquY!Qwydv$>%C<7J~P=Tzpc!iM0KL|qIq=Vi%0FG3Z8x^3ZE=v8X`hvr)U z^)cc0{p|GDUNx9}zE0ZPY_g;r?jpo4j`BRVXf-Ku9G^dO-x!s8jPQba+}3RDXBR@% zuU=xUol8& zH`}}jOmHni=AI#O1@rTf!f7LaUk`{CfU#6*=*JsBLZ78Zdx=uTmdq-=#IGXk4%3ny(a6|oj zkl`a3n_L7_x}TW(*&q3Ij}6K-ZQsBkb6TS zY_nae`zRvtRqj;2>e{Znyoxf1v6b8Y;R0mlXe-ekmUk)BR%u~j%ruw2T9y3iE6t&= z8;D@16WNpi3XSl0(%d#LU%oV~F8M0)c?&qG;$mY{mUJb= zfVS!SaCRJLBlrxRWC7G7;ARqJ#1Jkn?wo|(Kq}qfu<=ovH6Qo~%Tt4>&Lpwb0 z8sh(V1q7RP`k;VM6l>F?{`~U?JgWOdZe9c%9HvUj%7McOdq>qcr*~&g3|L=*Q2ZJI z{DIirlqQ?O3zg20I`hc-M!Z7>&BF;(Z^ySO+wok~g~G{o?G0GBH)#gtd`O+8#9q3x z`+mY->mZkNTqpadEcAMqc$!OB13^q>9yFYCxutFJF(h1Hl^0#JRfk^4CQy!YipvyD zj*Ra!hw1b;9jnC>ZGU^)up zeA4ARyt4|L9L+Q~0<2;1)UN`kTCsH4Qu5C8JC$O0Dl4AqVtobn*V;tDdnp#r12)gv z_79g~%5Fbb6VlYB3FEthH^cxh_W`uQJu@0xsps^H_*{zRuDVG(6t~3GE5F&WS2H@J zqWn4;l1%)BzxPG{e!{nzI(@UIqKv6LVop<%$?D5V_D%?)%Tb)c>hANF3Tn362l3hHDC?Qr&&$Wd)}P`Nen>}I?kNEw4ycDxl+P?pld#TW6k=RGl32Qw=;Ie3kAI}0S+HjM?mRCkxBS8J z4c8OSz-IPVfMVn%iSWKI!i? zIgtU%pSVFJ%rX!z5 z^1iJxkThUFxhf`l2*jW6yj{NOuV-lzaOIJc!S~Ldg>rL@AhVD;ghDKvzNzl6NX5n#Hk&_W<>_>0jP56D2K)9 z7J}Pk@7A~!+5NxM@)1r~xyoNx1R1?uG`UfaOyMiC3T*c~4DLz*XXSdV+@Uk_rVNhr@M4_kH~v^~fiHvKy?QrsHZ zN}Y6&rpfE7Ijp;MO#^e#dz)3h{`i}2LniV!$aXzD&wJvY6cd5BY?gdEX2#}jJ9u9d ztM(Y_(`uCF8FNb=b z4xb9ZQvD)csmv+enWa8J2*u@=bOB1e&}#@J={{JUfPzfF82Z6+ya zINOi4&}R(p1H#02p5mPz=R6gfzsI!fg*|t{++SsQQ>qrI2{%?0Nlp%buin0+-0!*o61G<@40oKMlM4ie!$|ydv(JyL4X{4@kJ~ulmWvY+s>i zEGB_BV>M?PJx&OBIGZ|c^&4@gE^TPe7O9w4oUogZ+6b%y-?aQhfPxN*-bnjKQxy2# zgcz>?Sg6j9Qeis2bhe(U`40`N4>04I_P@ZoULmX;z!{MEaF0)@O`(WGZC8M1QJ91) z<2Pqpz#F+MFJ4qd7IqxTAWtMp)F4i3u{m3zPscRMssUG z9$rLgA!RcEq*W(ahM5aBMz6C3q1WH=?(_ge^APrkW+iB+qvt}UbINTSvu!9G(d&dl z=EmF}hTpT8O=N=*xuTC>K~>c`k7RI5CA`u#f|&p4eHn#)!mAIJ*bMhv+ox%y9lye! zb9{(bFXNHH8Vp&@ioUn>l@}%Cl=_B5V%LS0t?pU9_(`2lSWfu8g9L0xJ1D)5c6l^n0GOn? zKOP5fzxA@mdgM_0N~X}lWHusc$+!V?&Qhtygc-7uf6G$yBhSd)Bo+yO zpj5cNf$aH3bHe?*EWURwwA51nSPU6(ka_h76Y*0=@@Toc9uCZ>ST9z}d~ZqbmT9@= zXR>1NSzJ`v|Hj5h(WomcuR%WCBa>_{=ZY`2=n2uD*PD!=IW2brgKk#E3$akC+uEnakz0b*S{bK`PpGq}!oASl{%rAO&!G?Q7^g6_wu zBb96(w9|#h{zWn1z_VnHpvFeCGa$M}T-^N;A4y0xDCQ#g`_Txj;Za!bqemTy-B}Iz z9!M_WaDtf5eMzlIMmMMNrXv1~?WJo^qu17D_|Uc)+p)X_BM-`Am^ROJoajti!qR?f zvCGcTi-oA6oaNvy{rFki59sl{Xk@NIy#|LOng5mmO*sSX5VazjovArYI4j21yqR*c z(5-7OiCj}t-@B#vNx_|%M9Kl@q9UT-#rO~|i0Jx%rz!K!isTZf!}q^^5|IhmP7_`5 zd6EvC;;~f6uo4`@uCI|on#x@c?^pV_t6D-X=m#|+a97O)68iuw!|78&-n)9 zB3w=Nb&SknZ2ye?th<+VXpJC+Ika_aOyxqp|)uZ6O zYI}&f8x3ha|G<8e+_5d_8ROLC+bS{Cfy;;1X^BU`$$jKDS`JkQ{5ctjZ<;*tQY2|}(%9DZMGTZg7qqDr>*mYnhme4qlLqcVLlJofs&A>EbK`M^a~Ibh znjU*E2rkNHNlU;Wn881P4$7|HbHeG4r~No0(mZ7Cy>}2rzbM71=aZ;6zHZhRf4d$~ zMQ?feae?QjO&z=IV22(GT!nj(if_uU$RaF%k%0hQH9^{HH>yVuUvZHx{oTr>E14K3 z^1?T;!l}(4b~@=cIQM{N|2*AkadB4h90g@*%Cr{6T7{2Zxz4 zph#awG9bY3WbGdOeM8rCxc-CPG%nJT?YE#mnRSBgK_%%#p3^l$A-6b>w68M)3m!cWUmCCN@ZjAKd%E>5U3tbrfO~zXk*%y-p z{jo~QiQJe#l4MPfN}`E8eCR?_=pLirk8G3Vg-BXUrd5z|Bu&oOuumrQ!#6C*Rp&n4+AQ4rCVS!98t=Gq>o$F%6|)L zG9$GhUHV-6^!(=L4V~kPpA9sPF=C}iCh)?N1A3xvZuE>z9BOg6`?~PkM?VXWYsQrX zlk8y96(xTnOU^v<4^^ojre6ocs%j&|$g(=>rMuzEmH5ENo3uw6D^;F7mLlr~5k>TqR>{YLJZwdGZka$gwTLQXsvw<^xi;l)Lw#1!kXXCzI= z*wyr%sg?>~;7&wG#zYB*g*@$&Uj$2ILy`!?rH!uRyXid%}OC?j@RlOZWEbz_Uu&FrY)ZN&{P|D`+ z?50KY*@2~p8@6I@;w+Z%9}L@Gkjtvd1Hq(Tep#u#yZ58sHPIR7%xnBmocgv6$lTKK z14rQG+4O-(FppTEp=JFoG1J01KiqM7Pv0KKS9cpE8a;3 zqM_wKoCnI@%lhzsFIqeE@U}l3y16UB((a;&9K>q=U$tCkR8v{kPE2SLK!zeMi5i8W zC{aL=E(na70mY%|P!i*)h=v}bkOTsP4up&aP(TD6M5@t2>4eZCB29!KH4vmoNdh7f zV)(A_`u@GY-*wlzf9_r9IeYJO_Vet!?moMpJCz0KH2nJE@ehdCs!zfG-$kSGwf=6* z+LuG^++TaiyFyc3>=`|)s=30O`5Cgp8BYXjN5)M?01i zgml>C{??wAMJy=BnzjXm&;q#bZM9D>cT|TR>}i{Ffz-^%@w3H~XL$zJGET*gUXkKV z*wkmnq%YK$@{w^}!$xKr>i6^WRi&fYa@$|pl z`Ug5dP2O3@2S7t04~k?Werof=f_UA?I-biO5z?awup$+|Hp zR%kLV?+o#S`0*_VzzsD6G301%^CEEW8XX;lN<(Bd)6sWr{ZZ}*UsP2hjEoO5Ue6dP2yU-?wh^iJI@A#V(*>)K=Hdk?R*h&C8l%TsFz_s z^Li9%d?nezAY~%;J1S1+gl&8^PAB47fhaZ}2w2>So3cz9Lg$|neKcTZ=@Hr-d!)Zd z9KH5qhY^)D#{`W=k5Z;e@h(;0#_>ifbi`geq5BPMf3yCMGOh`H`p=ZiE?>w>^@M8Aa`%pvS(g zyzCiaSmiM7OOpHi=7no#y>;Wb1;rws3}OI7rn?E<$I+C=6kL9K4`kxz$$VQkp}DPM zh>I?NY8o68w%&plTY z%d2BMp09!>tEI|3EOhd{3%^=YRlK?uzq_R?2D(v*JnFRh{Q(AIb=T$ha}8%ZP|~r| zCzwU4Ley=s$dJ+y&0aqj-7k#;a70_?Fvex5@eja~(il})tPcC; z4|sf-$+W(sVFpgooTm6Y@a9B3N3`RH=h{T#Zjpsv;y=2K!Qlie#c1I=oU$Z3jX_xn z4xMNXkRK!mB#o2QH*gv~u5e7rO0bM9gi8O!Y_2U~OfD}gk101b1)%)?9R8EZ>XWyJ zooXIalRhXV+W93te0E*n=wKp@%vC7zimBS=uJx5~JiJr!H^-M1$?zrck`JmlowH*s zX`R$lvBA+4M|xT0d`{#_ols(1za);)3_AqZ#_tKXKGrUuKm2esb#{xIDDM>k>T+4Y3YXxOD7ym-ZNtFMA^88g9MJs z^me*dQij%-xpbH40Wtj9LewoA+#MTZ`#6UwJQriT>|7JrFN!rLDw)wH(&2GN4JlW-%ZR z==(GWJ*7bDn&iJj@>htfp}w!6!ckIgNI|5oxw+^C5wmJ!?v3S7l?f^E~n zgH^C}o~Jge@@YgUjvbtUyTslXoBz`_A9KnU6ZYpp=OA<1CGQ!?sSsw>(qh12N_6zC zpjQ9qKR^cV^yiN!mZ6`{+%NI&-*&&+(scyMLMHiI(VjbcruMbfy@m05uC?f029=H3 z$msSZ$pj{5$LPLO`WDG`J}^8yUzH+v7+!I7JP1!nMVieGL8;^y089pgRd?c?6_J`+ zw{`GcBf@*$0ZTwm-14y*ko&iG`yELVINnk}L%ocHQk9Z7h-)C_AHuB^Gl158tlShd zk&cvU-9QQ{RKne}KezH3CoEkB5)Zd}cgBRm?1Qt_>fK>8ndHWod|#BfOoqBV8hCs~J;r$SAl~&0L8!hzy9qo7jkL_?Pj^wpFUtB}{s-t^uswZ>wZnUc?b79ID0ax5Mu$+`KpE08p|W z>-Rm@zWui)Jned614JA2;VFM*@>ojEheHkEkEXi8oLZ-!Bgtwgmy_}P;#-b)Wa=I7%U6{WJWgN?cNmpkWxO9 z%h&sj<0NUo0@+;W<=c_>uA94Z?V-Mds{ThPDy^p2gdj1$wyK1-Tf*q=w)?t^1!>ew zsiR*$KYd(_ZAMd-oC8GoLs{kxZ68+$&X!Q8jn2+`!j|mmp%sJ6yYd%~TprW~=E&XB z^?QfxE%0rf2K=WUsHUZd>Lwzl!Evr%e(f(tgL$8Z05{|iMkCUGP zjr=zX*NRl4L*sEuR}P@W>Qw_8XbHkC7=(HR3~O~YQ6?MR(ASbu?eN!LzYC^^20=p= ze@7e7l+t;3MT_v11aX`Z)pk9-ZS4S8dhp8OcXk2}red*0g$LQidQAB;|O-ql)6 z)g1hOBRWSXz(Bm0T3&ui4n=C-K9(YVAsJx>egll~iFl+IB)hyz`jQ;$RBdMwSFtqi zk7O5T2A$j|0%R$W2Rht*qvqYrtTtHXU04Dd>jy|+-+%j%jcN@*woV0B<_7-L1kOGp zE0fZ=2~SoW!(D#G7gDw-{j(X#_0VPFN#W+VUtYWt(b3BN-g{>CiIIQnYraC$9%(`K({psbo65$0k&Xnhbmj^x!MQXN^KU4^fX(Sv$jfu(|ZRb8l{=z8r`XRzng z#=i=6J5NZvTQvQ0UW-FA&Ma)iu;mfgda*mtEUGWD?Hl9*m&0;3=sbPmR1|fo^&>q< zi5HenNT$;OUOb;A*UBB+MM>rI!%km2LU0+l+526oV{cg=?bY7Wisyni^SM0T!}7+5 z=X>FbMPByso3EIjQCW;huUIuA)N=KhLXaCZil5#^Q;iF;r5KYN7kjQ@NFdV$I_-c( z#<^l%JE_k#k%T0=`u?RAp=nF8dHFfrQ?I$~&hH#N@VC+p+qt1FyrSe7rX>f*cDXJ?%G z=FL&*Yt#2nQ|mL)ZZ12l5!69|@vdIrhSxw&!IQuZkTMI3w@xs~z_lm z*9NFbOc|qPEVR$;UAL(e3b^vexQ0bu=jiM|gfb1XxW_GWKuun>dJ diff --git a/front_end/animation/AnimationModel.js b/front_end/animation/AnimationModel.js index f79c51853c..ca45d51288 100644 --- a/front_end/animation/AnimationModel.js +++ b/front_end/animation/AnimationModel.js @@ -264,7 +264,7 @@ WebInspector.AnimationModel.Animation.prototype = { */ name: function() { - return this.source().name(); + return this._payload.name; }, /** @@ -513,14 +513,6 @@ WebInspector.AnimationModel.AnimationEffect.prototype = { return this._payload.fill; }, - /** - * @return {string} - */ - name: function() - { - return this._payload.name; - }, - /** * @return {!Promise.} */ diff --git a/front_end/bindings/BlackboxManager.js b/front_end/bindings/BlackboxManager.js index 8751939299..a848f1a854 100644 --- a/front_end/bindings/BlackboxManager.js +++ b/front_end/bindings/BlackboxManager.js @@ -17,14 +17,13 @@ WebInspector.BlackboxManager = function(debuggerWorkspaceBinding, networkMapping WebInspector.moduleSetting("skipStackFramesPattern").addChangeListener(this._patternChanged.bind(this)); WebInspector.moduleSetting("skipContentScripts").addChangeListener(this._patternChanged.bind(this)); - /** @type {!Map>} */ - this._scriptIdToPositions = new Map(); + /** @type {!Map>>} */ + this._debuggerModelData = new Map(); /** @type {!Map} */ this._isBlackboxedURLCache = new Map(); } WebInspector.BlackboxManager.prototype = { - /** * @param {function(!WebInspector.Event)} listener * @param {!Object=} thisObject @@ -49,9 +48,9 @@ WebInspector.BlackboxManager.prototype = { */ isBlackboxedRawLocation: function(location) { - if (!this._scriptIdToPositions.has(location.scriptId)) - return false; - var positions = this._scriptIdToPositions.get(location.scriptId); + var positions = this._scriptPositions(location.script()); + if (!positions) + return this._isBlackboxedScript(location.script()); var index = positions.lowerBound(location, comparator); return !!(index % 2); @@ -78,19 +77,21 @@ WebInspector.BlackboxManager.prototype = { var isContentScript = projectType === WebInspector.projectTypes.ContentScripts; if (isContentScript && WebInspector.moduleSetting("skipContentScripts").get()) return true; - var networkURL = this._networkMapping.networkURL(uiSourceCode); - var url = projectType === WebInspector.projectTypes.Formatter ? uiSourceCode.url() : networkURL; - return this.isBlackboxedURL(url); + var url = this._uiSourceCodeURL(uiSourceCode); + return url ? this.isBlackboxedURL(url) : false; }, /** * @param {string} url + * @param {boolean=} isContentScript * @return {boolean} */ - isBlackboxedURL: function(url) + isBlackboxedURL: function(url, isContentScript) { if (this._isBlackboxedURLCache.has(url)) return !!this._isBlackboxedURLCache.get(url); + if (isContentScript && WebInspector.moduleSetting("skipContentScripts").get()) + return true; var regex = WebInspector.moduleSetting("skipStackFramesPattern").asRegExp(); var isBlackboxed = regex && regex.test(url); this._isBlackboxedURLCache.set(url, isBlackboxed); @@ -106,13 +107,13 @@ WebInspector.BlackboxManager.prototype = { { if (!sourceMap) return Promise.resolve(); - if (!this._scriptIdToPositions.has(script.scriptId)) + var previousScriptState = this._scriptPositions(script); + if (!previousScriptState) return Promise.resolve(); var mappings = sourceMap.mappings().slice(); mappings.sort(mappingComparator); - var previousScriptState = this._scriptIdToPositions.get(script.scriptId); if (!mappings.length) { if (previousScriptState.length > 0) return this._setScriptState(script, []).then(this._sourceMapLoadedForTest); @@ -153,18 +154,63 @@ WebInspector.BlackboxManager.prototype = { }, /** - * @param {string} url + * @param {!WebInspector.UISourceCode} uiSourceCode + * @return {?string} + */ + _uiSourceCodeURL: function(uiSourceCode) + { + var networkURL = this._networkMapping.networkURL(uiSourceCode); + var projectType = uiSourceCode.project().type(); + if (projectType === WebInspector.projectTypes.Debugger) + return null; + var url = projectType === WebInspector.projectTypes.Formatter ? uiSourceCode.url() : networkURL; + return url ? url : null; + }, + + /** + * @param {!WebInspector.UISourceCode} uiSourceCode * @return {boolean} */ - canBlackboxURL: function(url) + canBlackboxUISourceCode: function(uiSourceCode) { - return !!this._urlToRegExpString(url); + var url = this._uiSourceCodeURL(uiSourceCode); + return url ? !!this._urlToRegExpString(url) : false; + }, + + /** + * @param {!WebInspector.UISourceCode} uiSourceCode + */ + blackboxUISourceCode: function(uiSourceCode) + { + var url = this._uiSourceCodeURL(uiSourceCode); + if (url) + this._blackboxURL(url); + }, + + /** + * @param {!WebInspector.UISourceCode} uiSourceCode + */ + unblackboxUISourceCode: function(uiSourceCode) + { + var url = this._uiSourceCodeURL(uiSourceCode); + if (url) + this._unblackboxURL(url); + }, + + blackboxContentScripts: function() + { + WebInspector.moduleSetting("skipContentScripts").set(true); + }, + + unblackboxContentScripts: function() + { + WebInspector.moduleSetting("skipContentScripts").set(false); }, /** * @param {string} url */ - blackboxURL: function(url) + _blackboxURL: function(url) { var regexPatterns = WebInspector.moduleSetting("skipStackFramesPattern").getAsArray(); var regexValue = this._urlToRegExpString(url); @@ -186,13 +232,9 @@ WebInspector.BlackboxManager.prototype = { /** * @param {string} url - * @param {boolean} isContentScript */ - unblackbox: function(url, isContentScript) + _unblackboxURL: function(url) { - if (isContentScript) - WebInspector.moduleSetting("skipContentScripts").set(false); - var regexPatterns = WebInspector.moduleSetting("skipStackFramesPattern").getAsArray(); var regexValue = WebInspector.blackboxManager._urlToRegExpString(url); if (!regexValue) @@ -221,9 +263,8 @@ WebInspector.BlackboxManager.prototype = { var promises = []; for (var debuggerModel of WebInspector.DebuggerModel.instances()) { for (var scriptId in debuggerModel.scripts) { - let script = debuggerModel.scripts[scriptId]; + var script = debuggerModel.scripts[scriptId]; promises.push(this._addScript(script) - .then(() => this._debuggerWorkspaceBinding.sourceMapForScript(script)) .then(loadSourceMap.bind(this, script))); } } @@ -231,13 +272,12 @@ WebInspector.BlackboxManager.prototype = { /** * @param {!WebInspector.Script} script - * @param {?WebInspector.SourceMap} sourceMap * @return {!Promise} * @this {WebInspector.BlackboxManager} */ - function loadSourceMap(script, sourceMap) + function loadSourceMap(script) { - return this.sourceMapLoaded(script, sourceMap); + return this.sourceMapLoaded(script, this._debuggerWorkspaceBinding.sourceMapForScript(script)); } }, @@ -246,9 +286,13 @@ WebInspector.BlackboxManager.prototype = { // This method is sniffed in tests. }, - _globalObjectCleared: function() + /** + * @param {!WebInspector.Event} event + */ + _globalObjectCleared: function(event) { - this._scriptIdToPositions.clear(); + var debuggerModel = /** @type {!WebInspector.DebuggerModel} */ (event.target); + this._debuggerModelData.delete(debuggerModel); this._isBlackboxedURLCache.clear(); }, @@ -277,9 +321,30 @@ WebInspector.BlackboxManager.prototype = { */ _isBlackboxedScript: function(script) { - if (script.isContentScript() && WebInspector.moduleSetting("skipContentScripts").get()) - return true; - return this.isBlackboxedURL(script.sourceURL); + return this.isBlackboxedURL(script.sourceURL, script.isContentScript()); + }, + + /** + * @param {!WebInspector.Script} script + * @return {?Array} + */ + _scriptPositions: function(script) + { + if (this._debuggerModelData.has(script.debuggerModel)) + return this._debuggerModelData.get(script.debuggerModel).get(script.scriptId) || null; + return null; + }, + + /** + * @param {!WebInspector.Script} script + * @param {!Array} positions + */ + _setScriptPositions: function(script, positions) + { + var debuggerModel = script.debuggerModel; + if (!this._debuggerModelData.has(debuggerModel)) + this._debuggerModelData.set(debuggerModel, new Map()); + this._debuggerModelData.get(debuggerModel).set(script.scriptId, positions); }, /** @@ -289,9 +354,9 @@ WebInspector.BlackboxManager.prototype = { */ _setScriptState: function(script, positions) { - if (this._scriptIdToPositions.has(script.scriptId)) { + var previousScriptState = this._scriptPositions(script); + if (previousScriptState) { var hasChanged = false; - var previousScriptState = this._scriptIdToPositions.get(script.scriptId); hasChanged = previousScriptState.length !== positions.length; for (var i = 0; !hasChanged && i < positions.length; ++i) hasChanged = positions[i].line !== previousScriptState[i].line || positions[i].column !== previousScriptState[i].column; @@ -310,10 +375,17 @@ WebInspector.BlackboxManager.prototype = { */ function updateState(success) { - if (success) - this._scriptIdToPositions.set(script.scriptId, positions); - else if (!this._scriptIdToPositions.has(script.scriptId)) - this._scriptIdToPositions.set(script.scriptId, []); + if (success) { + this._setScriptPositions(script, positions); + this._debuggerWorkspaceBinding.updateLocations(script); + var isBlackboxed = positions.length !== 0; + if (!isBlackboxed && script.sourceMapURL) + this._debuggerWorkspaceBinding.maybeLoadSourceMap(script); + } else { + var hasPositions = !!this._scriptPositions(script); + if (!hasPositions) + this._setScriptPositions(script, []); + } } }, diff --git a/front_end/bindings/BreakpointManager.js b/front_end/bindings/BreakpointManager.js index 4e665e068d..53446c5b79 100644 --- a/front_end/bindings/BreakpointManager.js +++ b/front_end/bindings/BreakpointManager.js @@ -919,10 +919,13 @@ WebInspector.BreakpointManager.TargetBreakpoint.prototype = { /** * @param {!WebInspector.DebuggerModel.Location} location - * @param {!WebInspector.UILocation} uiLocation + * @param {!WebInspector.LiveLocation} liveLocation */ - _locationUpdated: function(location, uiLocation) + _locationUpdated: function(location, liveLocation) { + var uiLocation = liveLocation.uiLocation(); + if (!uiLocation) + return; var oldUILocation = this._uiLocations[location.id()] || null; this._uiLocations[location.id()] = uiLocation; this._breakpoint._replaceUILocation(oldUILocation, uiLocation); diff --git a/front_end/bindings/CSSWorkspaceBinding.js b/front_end/bindings/CSSWorkspaceBinding.js index 1502aa0ad9..62835f65bb 100644 --- a/front_end/bindings/CSSWorkspaceBinding.js +++ b/front_end/bindings/CSSWorkspaceBinding.js @@ -100,7 +100,7 @@ WebInspector.CSSWorkspaceBinding.prototype = { /** * @param {!WebInspector.CSSLocation} rawLocation - * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate + * @param {function(!WebInspector.LiveLocation)} updateDelegate * @return {!WebInspector.CSSWorkspaceBinding.LiveLocation} */ createLiveLocation: function(rawLocation, updateDelegate) @@ -314,7 +314,7 @@ WebInspector.CSSWorkspaceBinding.HeaderInfo.prototype = { * @param {?WebInspector.CSSStyleSheetHeader} header * @param {!WebInspector.CSSLocation} rawLocation * @param {!WebInspector.CSSWorkspaceBinding} binding - * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate + * @param {function(!WebInspector.LiveLocation)} updateDelegate */ WebInspector.CSSWorkspaceBinding.LiveLocation = function(cssModel, header, rawLocation, binding, updateDelegate) { @@ -397,6 +397,15 @@ WebInspector.CSSWorkspaceBinding.LiveLocation.prototype = { this._cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this); }, + /** + * @override + * @return {boolean} + */ + isBlackboxed: function() + { + return false; + }, + __proto__: WebInspector.LiveLocation.prototype } diff --git a/front_end/bindings/CompilerScriptMapping.js b/front_end/bindings/CompilerScriptMapping.js index e116c9f767..32b1832310 100644 --- a/front_end/bindings/CompilerScriptMapping.js +++ b/front_end/bindings/CompilerScriptMapping.js @@ -165,6 +165,20 @@ WebInspector.CompilerScriptMapping.prototype = { return this._sourceMapForScriptId[script.scriptId]; }, + /** + * @param {!WebInspector.Script} script + */ + maybeLoadSourceMap: function(script) + { + if (!script.sourceMapURL) + return; + if (this._pendingSourceMapLoadingCallbacks[script.sourceMapURL]) + return; + if (this._sourceMapForScriptId[script.scriptId]) + return; + this._processScript(script); + }, + /** * @param {!WebInspector.Event} event */ @@ -181,6 +195,8 @@ WebInspector.CompilerScriptMapping.prototype = { */ _processScript: function(script) { + if (WebInspector.blackboxManager.isBlackboxedURL(script.sourceURL, script.isContentScript())) + return; // Create stub UISourceCode for the time source mapping is being loaded. var stubUISourceCode = this._stubProject.addContentProvider(script.sourceURL, new WebInspector.StaticContentProvider(WebInspector.resourceTypes.Script, "\n\n\n\n\n// Please wait a bit.\n// Compiled script is not shown while source map is being loaded!", script.sourceURL)); this._stubUISourceCodes.set(script.scriptId, stubUISourceCode); diff --git a/front_end/bindings/DebuggerWorkspaceBinding.js b/front_end/bindings/DebuggerWorkspaceBinding.js index 0e90c6fc8a..250530696a 100644 --- a/front_end/bindings/DebuggerWorkspaceBinding.js +++ b/front_end/bindings/DebuggerWorkspaceBinding.js @@ -121,7 +121,7 @@ WebInspector.DebuggerWorkspaceBinding.prototype = { /** * @param {!WebInspector.DebuggerModel.Location} rawLocation - * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate + * @param {function(!WebInspector.LiveLocation)} updateDelegate * @return {!WebInspector.DebuggerWorkspaceBinding.Location} */ createLiveLocation: function(rawLocation, updateDelegate) @@ -133,9 +133,22 @@ WebInspector.DebuggerWorkspaceBinding.prototype = { return location; }, + /** + * @param {!Array} rawLocations + * @param {function(!WebInspector.LiveLocation)} updateDelegate + * @return {!WebInspector.LiveLocation} + */ + createStackTraceTopFrameLiveLocation: function(rawLocations, updateDelegate) + { + console.assert(rawLocations.length); + var location = new WebInspector.DebuggerWorkspaceBinding.StackTraceTopFrameLocation(rawLocations, this, updateDelegate); + location.update(); + return location; + }, + /** * @param {!WebInspector.DebuggerModel.CallFrame} callFrame - * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate + * @param {function(!WebInspector.LiveLocation)} updateDelegate * @return {!WebInspector.DebuggerWorkspaceBinding.Location} */ createCallFrameLiveLocation: function(callFrame, updateDelegate) @@ -242,6 +255,17 @@ WebInspector.DebuggerWorkspaceBinding.prototype = { return targetData._compilerMapping.sourceMapForScript(script); }, + /** + * @param {!WebInspector.Script} script + */ + maybeLoadSourceMap: function(script) + { + var targetData = this._targetToData.get(script.target()); + if (!targetData) + return; + targetData._compilerMapping.maybeLoadSourceMap(script); + }, + /** * @param {!WebInspector.Event} event */ @@ -512,7 +536,7 @@ WebInspector.DebuggerWorkspaceBinding.ScriptInfo.prototype = { * @param {!WebInspector.Script} script * @param {!WebInspector.DebuggerModel.Location} rawLocation * @param {!WebInspector.DebuggerWorkspaceBinding} binding - * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate + * @param {function(!WebInspector.LiveLocation)} updateDelegate */ WebInspector.DebuggerWorkspaceBinding.Location = function(script, rawLocation, binding, updateDelegate) { @@ -533,12 +557,99 @@ WebInspector.DebuggerWorkspaceBinding.Location.prototype = { return this._binding.rawLocationToUILocation(debuggerModelLocation); }, + /** + * @override + */ dispose: function() { WebInspector.LiveLocation.prototype.dispose.call(this); this._binding._removeLiveLocation(this); }, + /** + * @override + * @return {boolean} + */ + isBlackboxed: function() + { + return WebInspector.blackboxManager.isBlackboxedRawLocation(this._rawLocation); + }, + + __proto__: WebInspector.LiveLocation.prototype +} + +/** + * @constructor + * @extends {WebInspector.LiveLocation} + * @param {!Array} rawLocations + * @param {!WebInspector.DebuggerWorkspaceBinding} binding + * @param {function(!WebInspector.LiveLocation)} updateDelegate + */ +WebInspector.DebuggerWorkspaceBinding.StackTraceTopFrameLocation = function(rawLocations, binding, updateDelegate) +{ + WebInspector.LiveLocation.call(this, updateDelegate); + + this._updateScheduled = true; + /** @type {!Array} */ + this._locations = []; + for (var location of rawLocations) + this._locations.push(binding.createLiveLocation(location, this._scheduleUpdate.bind(this))); + this._updateLocation(); +} + +WebInspector.DebuggerWorkspaceBinding.StackTraceTopFrameLocation.prototype = { + /** + * @override + * @return {!WebInspector.UILocation} + */ + uiLocation: function() + { + return this._currentLocation().uiLocation(); + }, + + /** + * @override + */ + dispose: function() + { + for (var location of this._locations) + location.dispose(); + }, + + /** + * @override + * @return {boolean} + */ + isBlackboxed: function() + { + return this._currentLocation().isBlackboxed(); + }, + + _scheduleUpdate: function() + { + if (!this._updateScheduled) { + this._updateScheduled = true; + setImmediate(this._updateLocation.bind(this)); + } + }, + + /** + * @return {!WebInspector.DebuggerWorkspaceBinding.Location} + */ + _currentLocation: function() + { + return this._locations[this._current < this._locations.length ? this._current : 0]; + }, + + _updateLocation: function() + { + this._updateScheduled = false; + this._current = 0; + while (this._current < this._locations.length && this._locations[this._current].isBlackboxed()) + ++this._current; + this.update(); + }, + __proto__: WebInspector.LiveLocation.prototype } diff --git a/front_end/bindings/LiveLocation.js b/front_end/bindings/LiveLocation.js index 2cc65c3a03..50bd67c120 100644 --- a/front_end/bindings/LiveLocation.js +++ b/front_end/bindings/LiveLocation.js @@ -4,7 +4,7 @@ /** * @constructor - * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate + * @param {function(!WebInspector.LiveLocation)} updateDelegate */ WebInspector.LiveLocation = function(updateDelegate) { @@ -14,11 +14,7 @@ WebInspector.LiveLocation = function(updateDelegate) WebInspector.LiveLocation.prototype = { update: function() { - var uiLocation = this.uiLocation(); - if (!uiLocation) - return; - if (this._updateDelegate(uiLocation)) - this.dispose(); + this._updateDelegate(this); }, /** @@ -32,5 +28,13 @@ WebInspector.LiveLocation.prototype = { dispose: function() { // Overridden by subclasses. + }, + + /** + * @return {boolean} + */ + isBlackboxed: function() + { + throw "Not implemented"; } } diff --git a/front_end/bindings/PresentationConsoleMessageHelper.js b/front_end/bindings/PresentationConsoleMessageHelper.js index 0f66b56b22..7d54c7041d 100644 --- a/front_end/bindings/PresentationConsoleMessageHelper.js +++ b/front_end/bindings/PresentationConsoleMessageHelper.js @@ -173,12 +173,15 @@ WebInspector.PresentationConsoleMessage = function(message, rawLocation) WebInspector.PresentationConsoleMessage.prototype = { /** - * @param {!WebInspector.UILocation} uiLocation + * @param {!WebInspector.LiveLocation} liveLocation */ - _updateLocation: function(uiLocation) + _updateLocation: function(liveLocation) { if (this._uiMessage) this._uiMessage.remove(); + var uiLocation = liveLocation.uiLocation(); + if (!uiLocation) + return; this._uiMessage = uiLocation.uiSourceCode.addLineMessage(this._level, this._text, uiLocation.lineNumber, uiLocation.columnNumber); }, diff --git a/front_end/common/TestBase.js b/front_end/common/TestBase.js index 5b9cb4bef7..48fecf7b69 100644 --- a/front_end/common/TestBase.js +++ b/front_end/common/TestBase.js @@ -79,6 +79,7 @@ WebInspector.TestBase.prototype.releaseControl = function() clearTimeout(this.timerId_); this.timerId_ = -1; } + this.controlTaken_ = false; this.reportOk_(); }; @@ -107,7 +108,7 @@ WebInspector.TestBase.prototype.reportFailure_ = function(error) /** * Run specified test on a fresh instance of the test suite. - * @param {string} name Name of a test method from implementation class. + * @param {Array} args method name followed by its parameters. */ WebInspector.TestBase.prototype.dispatch = function(args) { @@ -122,6 +123,19 @@ WebInspector.TestBase.prototype.dispatch = function(args) }; +/** + * Wrap an async method with TestBase.{takeControl(), releaseControl()} + * and invoke TestBase.reportOk_ upon completion. + * @param {Array} args method name followed by its parameters. + */ +WebInspector.TestBase.prototype.waitForAsync = function(var_args) +{ + var args = Array.prototype.slice.call(arguments); + this.takeControl(); + args.push(this.releaseControl.bind(this)); + this.dispatch(args); +}; + /** * Overrides the method with specified name until it's called first time. * @param {!Object} receiver An object whose method to override. diff --git a/front_end/common/TextRange.js b/front_end/common/TextRange.js index e9d4e9af64..43f21c1f90 100644 --- a/front_end/common/TextRange.js +++ b/front_end/common/TextRange.js @@ -316,18 +316,42 @@ WebInspector.SourceRange = function(offset, length) this.length = length; } +WebInspector.SourceRange.prototype = { + /** + * @param {string} text + * @return {!WebInspector.TextRange} + */ + toTextRange: function(text) + { + var p1 = fromOffset(text, this.offset); + var p2 = fromOffset(text, this.offset + this.length); + return new WebInspector.TextRange(p1.lineNumber, p1.columnNumber, p2.lineNumber, p2.columnNumber); + + /** + * @param {string} text + * @param {number} offset + * @return {!{lineNumber: number, columnNumber: number}} + */ + function fromOffset(text, offset) + { + var lineEndings = text.lineEndings(); + var lineNumber = lineEndings.lowerBound(offset); + var columnNumber = lineNumber === 0 ? offset : offset - lineEndings[lineNumber - 1] - 1; + return {lineNumber: lineNumber, columnNumber: columnNumber}; + } + } +} + /** * @constructor * @param {string} sourceURL * @param {!WebInspector.TextRange} oldRange - * @param {string} oldText * @param {string} newText */ -WebInspector.SourceEdit = function(sourceURL, oldRange, oldText, newText) +WebInspector.SourceEdit = function(sourceURL, oldRange, newText) { this.sourceURL = sourceURL; this.oldRange = oldRange; - this.oldText = oldText; this.newText = newText; } diff --git a/front_end/components/DebuggerPresentationUtils.js b/front_end/components/DebuggerPresentationUtils.js deleted file mode 100644 index 83700e8a7a..0000000000 --- a/front_end/components/DebuggerPresentationUtils.js +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -WebInspector.DebuggerPresentationUtils = {} - -/** - * @param {?WebInspector.DebuggerModel} debuggerModel - * @param {!RuntimeAgent.StackTrace=} stackTrace - * @param {boolean=} showBlackboxed - * @return {?RuntimeAgent.CallFrame} - */ -WebInspector.DebuggerPresentationUtils.callFrameAnchorFromStackTrace = function(debuggerModel, stackTrace, showBlackboxed) -{ - /** - * @param {!Array.} callFrames - * @return {?RuntimeAgent.CallFrame} - */ - function innerCallFrameAnchorFromStackTrace(callFrames) - { - if (!callFrames.length) - return null; - if (showBlackboxed) - return callFrames[0]; - for (var callFrame of callFrames) { - var location = debuggerModel && debuggerModel.createRawLocationByScriptId(callFrame.scriptId, callFrame.lineNumber, callFrame.columnNumber); - var blackboxed = location ? - WebInspector.blackboxManager.isBlackboxedRawLocation(location) : - WebInspector.blackboxManager.isBlackboxedURL(callFrame.url); - if (!blackboxed) - return callFrame; - } - return null; - } - - var asyncStackTrace = stackTrace; - while (asyncStackTrace) { - var callFrame = innerCallFrameAnchorFromStackTrace(asyncStackTrace.callFrames); - if (callFrame) - return callFrame; - asyncStackTrace = asyncStackTrace.parent; - } - return stackTrace && stackTrace.callFrames.length ? stackTrace.callFrames[0] : null; -} diff --git a/front_end/components/Linkifier.js b/front_end/components/Linkifier.js index f7804d5f9d..439eefdb30 100644 --- a/front_end/components/Linkifier.js +++ b/front_end/components/Linkifier.js @@ -39,8 +39,9 @@ WebInspector.LinkifierFormatter.prototype = { /** * @param {!Element} anchor * @param {!WebInspector.UILocation} uiLocation + * @param {boolean} isBlackboxed */ - formatLiveAnchor: function(anchor, uiLocation) { } + formatLiveAnchor: function(anchor, uiLocation, isBlackboxed) { } } /** @@ -199,18 +200,33 @@ WebInspector.Linkifier.prototype = { */ linkifyConsoleCallFrame: function(target, callFrame, classes) { - // FIXME(62725): console stack trace line/column numbers are one-based. - var lineNumber = callFrame.lineNumber ? callFrame.lineNumber - 1 : 0; - var columnNumber = callFrame.columnNumber ? callFrame.columnNumber - 1 : 0; - var anchor = this.linkifyScriptLocation(target, callFrame.scriptId, callFrame.url, lineNumber, columnNumber, classes); + return this.linkifyScriptLocation(target, callFrame.scriptId, callFrame.url, WebInspector.DebuggerModel.fromOneBased(callFrame.lineNumber), WebInspector.DebuggerModel.fromOneBased(callFrame.columnNumber), classes); + }, + + /** + * @param {!WebInspector.Target} target + * @param {!RuntimeAgent.StackTrace} stackTrace + * @param {string=} classes + * @return {!Element} + */ + linkifyStackTraceTopFrame: function(target, stackTrace, classes) + { + console.assert(stackTrace.callFrames && stackTrace.callFrames.length); + + var topFrame = stackTrace.callFrames[0]; + var fallbackAnchor = WebInspector.linkifyResourceAsNode(topFrame.url, WebInspector.DebuggerModel.fromOneBased(topFrame.lineNumber), WebInspector.DebuggerModel.fromOneBased(topFrame.columnNumber), classes); + if (target.isDetached()) + return fallbackAnchor; + var debuggerModel = WebInspector.DebuggerModel.fromTarget(target); - var location = debuggerModel && debuggerModel.createRawLocationByScriptId(callFrame.scriptId, callFrame.lineNumber, callFrame.columnNumber); - var blackboxed = location ? - WebInspector.blackboxManager.isBlackboxedRawLocation(location) : - WebInspector.blackboxManager.isBlackboxedURL(callFrame.url); - if (blackboxed) - anchor.classList.add("webkit-html-blackbox-link"); + var rawLocations = debuggerModel.createRawLocationsByStackTrace(stackTrace); + if (rawLocations.length === 0) + return fallbackAnchor; + var anchor = this._createAnchor(classes); + var liveLocation = WebInspector.debuggerWorkspaceBinding.createStackTraceTopFrameLiveLocation(rawLocations, this._updateAnchor.bind(this, anchor)); + this._liveLocationsByTarget.get(target).set(anchor, liveLocation); + anchor[WebInspector.Linkifier._fallbackAnchorSymbol] = fallbackAnchor; return anchor; }, @@ -290,12 +306,15 @@ WebInspector.Linkifier.prototype = { /** * @param {!Element} anchor - * @param {!WebInspector.UILocation} uiLocation + * @param {!WebInspector.LiveLocation} liveLocation */ - _updateAnchor: function(anchor, uiLocation) + _updateAnchor: function(anchor, liveLocation) { + var uiLocation = liveLocation.uiLocation(); + if (!uiLocation) + return; anchor[WebInspector.Linkifier._uiLocationSymbol] = uiLocation; - this._formatter.formatLiveAnchor(anchor, uiLocation); + this._formatter.formatLiveAnchor(anchor, uiLocation, liveLocation.isBlackboxed()); } } @@ -323,8 +342,9 @@ WebInspector.Linkifier.DefaultFormatter.prototype = { * @override * @param {!Element} anchor * @param {!WebInspector.UILocation} uiLocation + * @param {boolean} isBlackboxed */ - formatLiveAnchor: function(anchor, uiLocation) + formatLiveAnchor: function(anchor, uiLocation, isBlackboxed) { var text = uiLocation.linkText(); text = text.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, "$1\u2026"); @@ -336,6 +356,8 @@ WebInspector.Linkifier.DefaultFormatter.prototype = { if (typeof uiLocation.lineNumber === "number") titleText += ":" + (uiLocation.lineNumber + 1); anchor.title = titleText; + + anchor.classList.toggle("webkit-html-blackbox-link", isBlackboxed); } } @@ -355,10 +377,11 @@ WebInspector.Linkifier.DefaultCSSFormatter.prototype = { * @override * @param {!Element} anchor * @param {!WebInspector.UILocation} uiLocation + * @param {boolean} isBlackboxed */ - formatLiveAnchor: function(anchor, uiLocation) + formatLiveAnchor: function(anchor, uiLocation, isBlackboxed) { - WebInspector.Linkifier.DefaultFormatter.prototype.formatLiveAnchor.call(this, anchor, uiLocation); + WebInspector.Linkifier.DefaultFormatter.prototype.formatLiveAnchor.call(this, anchor, uiLocation, isBlackboxed); anchor.classList.add("webkit-html-resource-link"); anchor.setAttribute("data-uncopyable", anchor.textContent); anchor.textContent = ""; diff --git a/front_end/network/NetworkConditionsSelector.js b/front_end/components/NetworkConditionsSelector.js similarity index 62% rename from front_end/network/NetworkConditionsSelector.js rename to front_end/components/NetworkConditionsSelector.js index b2796daf95..80e853257d 100644 --- a/front_end/network/NetworkConditionsSelector.js +++ b/front_end/components/NetworkConditionsSelector.js @@ -4,12 +4,13 @@ /** * @constructor - * @param {!HTMLSelectElement} selectElement + * @param {function(!Array):!Array} populateCallback + * @param {function(number)} selectCallback */ -WebInspector.NetworkConditionsSelector = function(selectElement) +WebInspector.NetworkConditionsSelector = function(populateCallback, selectCallback) { - this._selectElement = selectElement; - this._selectElement.addEventListener("change", this._optionSelected.bind(this), false); + this._populateCallback = populateCallback; + this._selectCallback = selectCallback; this._customSetting = WebInspector.moduleSetting("networkConditionsCustomProfiles"); this._customSetting.addChangeListener(this._populateOptions, this); this._manager = WebInspector.multitargetNetworkManager; @@ -20,11 +21,14 @@ WebInspector.NetworkConditionsSelector = function(selectElement) /** @typedef {!{title: string, value: !WebInspector.NetworkManager.Conditions}} */ WebInspector.NetworkConditionsProfile; +/** @typedef {!{title: string, items: !Array}} */ +WebInspector.NetworkConditionsProfileGroup; + /** * @param {number} throughput * @return {string} */ -WebInspector.NetworkConditionsSelector.throughputText = function(throughput) +WebInspector.NetworkConditionsSelector._throughputText = function(throughput) { if (throughput < 0) return ""; @@ -52,76 +56,172 @@ WebInspector.NetworkConditionsSelector._networkConditionsPresets = [ /** @type {!WebInspector.NetworkConditionsProfile} */ WebInspector.NetworkConditionsSelector._disabledPreset = {title: "No throttling", value: {download: -1, upload: -1, latency: 0}}; +/** + * @param {!WebInspector.NetworkConditionsProfile} preset + * @return {!{text: string, title: string}} + */ +WebInspector.NetworkConditionsSelector._profileTitle = function(preset) +{ + var downloadInKbps = preset.value.download / (1024 / 8); + var uploadInKbps = preset.value.upload / (1024 / 8); + var isThrottling = (downloadInKbps >= 0) || (uploadInKbps >= 0) || (preset.value.latency > 0); + var presetTitle = WebInspector.UIString(preset.title); + if (!isThrottling) + return {text: presetTitle, title: presetTitle}; + + var downloadText = WebInspector.NetworkConditionsSelector._throughputText(preset.value.download); + var uploadText = WebInspector.NetworkConditionsSelector._throughputText(preset.value.upload); + var title = WebInspector.UIString("%s (%s\u2b07 %s\u2b06 %dms RTT)", presetTitle, downloadText, uploadText, preset.value.latency); + return {text: title, title: WebInspector.UIString("Maximum download throughput: %s.\r\nMaximum upload throughput: %s.\r\nMinimum round-trip time: %dms.", downloadText, uploadText, preset.value.latency)}; +} + WebInspector.NetworkConditionsSelector.prototype = { _populateOptions: function() { - this._selectElement.removeChildren(); - - var customGroup = this._addGroup(this._customSetting.get(), WebInspector.UIString("Custom")); - customGroup.insertBefore(new Option(WebInspector.UIString("Add\u2026"), WebInspector.UIString("Add\u2026")), customGroup.firstChild); - - this._addGroup(WebInspector.NetworkConditionsSelector._networkConditionsPresets, WebInspector.UIString("Presets")); - this._addGroup([WebInspector.NetworkConditionsSelector._disabledPreset], WebInspector.UIString("Disabled")); + var customGroup = {title: WebInspector.UIString("Custom"), items: this._customSetting.get()}; + var presetsGroup = {title: WebInspector.UIString("Presets"), items: WebInspector.NetworkConditionsSelector._networkConditionsPresets}; + var disabledGroup = {title: WebInspector.UIString("Disabled"), items: [WebInspector.NetworkConditionsSelector._disabledPreset]}; + this._options = this._populateCallback([customGroup, presetsGroup, disabledGroup]); + this._conditionsChanged(); + }, + revealAndUpdate: function() + { + WebInspector.Revealer.reveal(this._customSetting); this._conditionsChanged(); }, /** - * @param {!Array.} presets - * @param {string} groupName - * @return {!Element} + * @param {!WebInspector.NetworkManager.Conditions} conditions */ - _addGroup: function(presets, groupName) + optionSelected: function(conditions) { - var groupElement = this._selectElement.createChild("optgroup"); - groupElement.label = groupName; - for (var i = 0; i < presets.length; ++i) { - var preset = presets[i]; - var downloadInKbps = preset.value.download / (1024 / 8); - var uploadInKbps = preset.value.upload / (1024 / 8); - var isThrottling = (downloadInKbps >= 0) || (uploadInKbps >= 0) || (preset.value.latency > 0); - var option; - var presetTitle = WebInspector.UIString(preset.title); - if (!isThrottling) { - option = new Option(presetTitle, presetTitle); - } else { - var downloadText = WebInspector.NetworkConditionsSelector.throughputText(preset.value.download); - var uploadText = WebInspector.NetworkConditionsSelector.throughputText(preset.value.upload); - var title = WebInspector.UIString("%s (%s\u2b07 %s\u2b06 %dms RTT)", presetTitle, downloadText, uploadText, preset.value.latency); - option = new Option(title, presetTitle); - option.title = WebInspector.UIString("Maximum download throughput: %s.\r\nMaximum upload throughput: %s.\r\nMinimum round-trip time: %dms.", downloadText, uploadText, preset.value.latency); - } - option.conditions = preset.value; - groupElement.appendChild(option); - } - return groupElement; + this._manager.setNetworkConditions(conditions); }, - _optionSelected: function() + _conditionsChanged: function() { - if (this._selectElement.selectedIndex === 0) { - WebInspector.Revealer.reveal(this._customSetting); - this._conditionsChanged(); - return; + var value = this._manager.networkConditions(); + for (var index = 0; index < this._options.length; ++index) { + var option = this._options[index]; + if (!option.conditions) + continue; + if (option.conditions.download === value.download && option.conditions.upload === value.upload && option.conditions.latency === value.latency) + this._selectCallback(index); } + } +} - this._manager.removeEventListener(WebInspector.MultitargetNetworkManager.Events.ConditionsChanged, this._conditionsChanged, this); - this._manager.setNetworkConditions(this._selectElement.options[this._selectElement.selectedIndex].conditions); - this._manager.addEventListener(WebInspector.MultitargetNetworkManager.Events.ConditionsChanged, this._conditionsChanged, this); - }, +/** + * @param {!HTMLSelectElement} selectElement + */ +WebInspector.NetworkConditionsSelector.decorateSelect = function(selectElement) +{ + var options = []; + var selector = new WebInspector.NetworkConditionsSelector(populate, select); + selectElement.addEventListener("change", optionSelected, false); - _conditionsChanged: function() + /** + * @param {!Array.} groups + * @return {!Array} + */ + function populate(groups) { - var value = this._manager.networkConditions(); - var options = this._selectElement.options; - for (var index = 1; index < options.length; ++index) { - var option = options[index]; - if (option.conditions.download === value.download && option.conditions.upload === value.upload && option.conditions.latency === value.latency) - this._selectElement.selectedIndex = index; + selectElement.removeChildren(); + options = []; + for (var i = 0; i < groups.length; ++i) { + var group = groups[i]; + var groupElement = selectElement.createChild("optgroup"); + groupElement.label = group.title; + if (!i) { + groupElement.appendChild(new Option(WebInspector.UIString("Add\u2026"), WebInspector.UIString("Add\u2026"))); + options.push({conditions: null}); + } + for (var preset of group.items) { + var title = WebInspector.NetworkConditionsSelector._profileTitle(preset); + var option = new Option(title.text, title.text); + option.title = title.title; + groupElement.appendChild(option); + options.push({conditions: preset.value}); + } } + return options; + } + + function optionSelected() + { + if (selectElement.selectedIndex === 0) + selector.revealAndUpdate(); + else + selector.optionSelected(options[selectElement.selectedIndex].conditions); + } + + /** + * @param {number} index + */ + function select(index) + { + if (selectElement.selectedIndex !== index) + selectElement.selectedIndex = index; } } +/** + * @return {!WebInspector.ToolbarMenuButton} + */ +WebInspector.NetworkConditionsSelector.createToolbarMenuButton = function() +{ + var button = new WebInspector.ToolbarMenuButton(appendItems); + button.setGlyph(""); + button.turnIntoSelect(); + + /** @type {!Array} */ + var items = []; + var selectedIndex = -1; + var selector = new WebInspector.NetworkConditionsSelector(populate, select); + return button; + + /** + * @param {!WebInspector.ContextMenu} contextMenu + */ + function appendItems(contextMenu) + { + for (var index = 0; index < items.length; ++index) { + var item = items[index]; + if (item.separator) + contextMenu.appendSeparator(); + else + contextMenu.appendCheckboxItem(item.title, selector.optionSelected.bind(selector, item.conditions), selectedIndex === index); + } + contextMenu.appendItem(WebInspector.UIString("Edit\u2026"), selector.revealAndUpdate.bind(selector)); + } + + /** + * @param {!Array.} groups + * @return {!Array} + */ + function populate(groups) + { + items = []; + for (var group of groups) { + for (var preset of group.items) { + var title = WebInspector.NetworkConditionsSelector._profileTitle(preset); + items.push({separator: false, text: preset.title, title: title.text, conditions: preset.value}); + } + items.push({separator: true, text: "", title: "", conditions: null}); + } + return /** @type {!Array} */ (items); + } + + /** + * @param {number} index + */ + function select(index) + { + selectedIndex = index; + button.setText(items[index].text); + } +} /** * @constructor @@ -131,7 +231,7 @@ WebInspector.NetworkConditionsSelector.prototype = { WebInspector.NetworkConditionsSettingsTab = function() { WebInspector.VBox.call(this, true); - this.registerRequiredCSS("network/networkConditionsSettingsTab.css"); + this.registerRequiredCSS("components/networkConditionsSettingsTab.css"); this.contentElement.createChild("div", "header").textContent = WebInspector.UIString("Network Throttling Profiles"); @@ -140,7 +240,7 @@ WebInspector.NetworkConditionsSettingsTab = function() this._list = new WebInspector.ListWidget(this); this._list.element.classList.add("conditions-list"); - this._list.registerRequiredCSS("network/networkConditionsSettingsTab.css"); + this._list.registerRequiredCSS("components/networkConditionsSettingsTab.css"); this._list.show(this.contentElement); this._customSetting = WebInspector.moduleSetting("networkConditionsCustomProfiles"); @@ -192,9 +292,9 @@ WebInspector.NetworkConditionsSettingsTab.prototype = { titleText.textContent = conditions.title; titleText.title = conditions.title; element.createChild("div", "conditions-list-separator"); - element.createChild("div", "conditions-list-text").textContent = WebInspector.NetworkConditionsSelector.throughputText(conditions.value.download); + element.createChild("div", "conditions-list-text").textContent = WebInspector.NetworkConditionsSelector._throughputText(conditions.value.download); element.createChild("div", "conditions-list-separator"); - element.createChild("div", "conditions-list-text").textContent = WebInspector.NetworkConditionsSelector.throughputText(conditions.value.upload); + element.createChild("div", "conditions-list-text").textContent = WebInspector.NetworkConditionsSelector._throughputText(conditions.value.upload); element.createChild("div", "conditions-list-separator"); element.createChild("div", "conditions-list-text").textContent = WebInspector.UIString("%dms", conditions.value.latency); return element; diff --git a/front_end/components/module.json b/front_end/components/module.json index 8310cd7822..5f89d98bff 100644 --- a/front_end/components/module.json +++ b/front_end/components/module.json @@ -14,6 +14,22 @@ "marker": "breakpoint-marker", "title": "DOM Breakpoint", "color": "rgb(105, 140, 254)" + }, + { + "type": "setting", + "settingName": "networkConditionsCustomProfiles", + "settingType": "array", + "defaultValue": [] + }, + { + "type": "settings-view", + "name": "network-conditions", + "title": "Throttling", + "order": "35", + "className": "WebInspector.NetworkConditionsSettingsTab", + "settings": [ + "networkConditionsCustomProfiles" + ] } ], "dependencies": [ @@ -25,7 +41,6 @@ "BreakpointsSidebarPaneBase.js", "CustomPreviewSection.js", "DataSaverInfobar.js", - "DebuggerPresentationUtils.js", "DOMBreakpointsSidebarPane.js", "DOMPresentationUtils.js", "DockController.js", @@ -35,6 +50,7 @@ "HandlerRegistry.js", "InspectorView.js", "Linkifier.js", + "NetworkConditionsSelector.js", "ObjectPopoverHelper.js", "ObjectPropertiesSection.js", "RemoteObjectPreviewFormatter.js", @@ -49,6 +65,7 @@ "eventListenersView.css", "domUtils.css", "inspectorViewTabbedPane.css", + "networkConditionsSettingsTab.css", "objectPropertiesSection.css", "objectValue.css" ] diff --git a/front_end/network/networkConditionsSettingsTab.css b/front_end/components/networkConditionsSettingsTab.css similarity index 100% rename from front_end/network/networkConditionsSettingsTab.css rename to front_end/components/networkConditionsSettingsTab.css diff --git a/front_end/console/ConsoleView.js b/front_end/console/ConsoleView.js index cef8e5226b..bee8c32b36 100644 --- a/front_end/console/ConsoleView.js +++ b/front_end/console/ConsoleView.js @@ -1173,7 +1173,7 @@ WebInspector.ConsoleCommand.prototype = { this._element.message = this; this._formattedCommand = createElementWithClass("span", "console-message-text source-code"); - this._formattedCommand.textContent = this.text; + this._formattedCommand.textContent = this.text.replaceControlCharacters(); this._element.appendChild(this._formattedCommand); var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/javascript", true); diff --git a/front_end/console/ConsoleViewMessage.js b/front_end/console/ConsoleViewMessage.js index 0de41e3f99..bb30b7b0b3 100644 --- a/front_end/console/ConsoleViewMessage.js +++ b/front_end/console/ConsoleViewMessage.js @@ -213,11 +213,8 @@ WebInspector.ConsoleViewMessage.prototype = { if (consoleMessage.scriptId) { this._anchorElement = this._linkifyScriptId(consoleMessage.scriptId, consoleMessage.url || "", consoleMessage.line, consoleMessage.column); } else { - var showBlackboxed = (consoleMessage.source !== WebInspector.ConsoleMessage.MessageSource.ConsoleAPI); - var debuggerModel = WebInspector.DebuggerModel.fromTarget(this._target()); - var callFrame = WebInspector.DebuggerPresentationUtils.callFrameAnchorFromStackTrace(debuggerModel, consoleMessage.stackTrace, showBlackboxed); - if (callFrame && callFrame.scriptId) - this._anchorElement = this._linkifyCallFrame(callFrame); + if (consoleMessage.stackTrace && consoleMessage.stackTrace.callFrames.length) + this._anchorElement = this._linkifyStackTraceTopFrame(consoleMessage.stackTrace); else if (consoleMessage.url && consoleMessage.url !== "undefined") this._anchorElement = this._linkifyLocation(consoleMessage.url, consoleMessage.line, consoleMessage.column); } @@ -276,13 +273,15 @@ WebInspector.ConsoleViewMessage.prototype = { }, /** - * @param {!RuntimeAgent.CallFrame} callFrame + * @param {!RuntimeAgent.StackTrace} stackTrace * @return {?Element} */ - _linkifyCallFrame: function(callFrame) + _linkifyStackTraceTopFrame: function(stackTrace) { var target = this._target(); - return this._linkifier.linkifyConsoleCallFrame(target, callFrame, "console-message-url"); + if (!target) + return null; + return this._linkifier.linkifyStackTraceTopFrame(target, stackTrace, "console-message-url"); }, /** diff --git a/front_end/console/consoleView.css b/front_end/console/consoleView.css index 1aa3b97fef..773921b7c7 100644 --- a/front_end/console/consoleView.css +++ b/front_end/console/consoleView.css @@ -404,7 +404,7 @@ flex-shrink: 0; } -.console-message-text { +.console-message-text .object-value-string { white-space: pre-wrap; } diff --git a/front_end/elements/ComputedStyleWidget.js b/front_end/elements/ComputedStyleWidget.js index 9aab9a7af2..bc6dced1d5 100644 --- a/front_end/elements/ComputedStyleWidget.js +++ b/front_end/elements/ComputedStyleWidget.js @@ -167,9 +167,11 @@ WebInspector.ComputedStyleWidget.prototype = { colon.textContent = ":"; propertyNameElement.appendChild(colon); - var propertyValueElement = renderer.renderValue(); - propertyValueElement.classList.add("property-value"); - propertyElement.appendChild(propertyValueElement); + var propertyValueElement = propertyElement.createChild("span", "property-value"); + + var propertyValueText = renderer.renderValue(); + propertyValueText.classList.add("property-value-text"); + propertyValueElement.appendChild(propertyValueText); var semicolon = createElementWithClass("span", "delimeter"); semicolon.textContent = ";"; diff --git a/front_end/elements/ElementsPanel.js b/front_end/elements/ElementsPanel.js index d9d112afd3..077af779bf 100644 --- a/front_end/elements/ElementsPanel.js +++ b/front_end/elements/ElementsPanel.js @@ -385,6 +385,15 @@ WebInspector.ElementsPanel.prototype = { if (selectedNode) { selectedNode.setAsInspectedNode(); this._lastValidSelectedNode = selectedNode; + + var executionContexts = selectedNode.target().runtimeModel.executionContexts(); + var nodeFrameId = selectedNode.frameId(); + for (var context of executionContexts) { + if (context.frameId == nodeFrameId) { + WebInspector.context.setFlavor(WebInspector.ExecutionContext, context); + break; + } + } } WebInspector.notifications.dispatchEventToListeners(WebInspector.NotificationService.Events.SelectedNodeChanged); this._selectedNodeChangedForTest(); diff --git a/front_end/elements/computedStyleSidebarPane.css b/front_end/elements/computedStyleSidebarPane.css index 84a06ad2bb..34828965ec 100644 --- a/front_end/elements/computedStyleSidebarPane.css +++ b/front_end/elements/computedStyleSidebarPane.css @@ -10,18 +10,31 @@ .computed-style-property { display: flex; - flex-wrap: wrap; + overflow: hidden; + flex: auto; } .computed-style-property .property-name { - width: 16em; + min-width: 5em; text-overflow: ellipsis; overflow: hidden; + flex-shrink: 1; + flex-basis: 16em; + flex-grow: 1; } .computed-style-property .property-value { - min-width: 5em; + margin-left: 2em; position: relative; + display: flex; + flex-shrink: 0; + flex-basis: 5em; + flex-grow: 10; +} + +.computed-style-property .property-value-text { + overflow: hidden; + text-overflow: ellipsis; } .tree-outline li:hover .goto-source-icon { @@ -38,7 +51,6 @@ position: absolute; top: -6px; left: -27px; - transform: scale(0.8); } .goto-source-icon:hover { diff --git a/front_end/elements/module.json b/front_end/elements/module.json index 565d0bfbef..717fe426a5 100644 --- a/front_end/elements/module.json +++ b/front_end/elements/module.json @@ -55,15 +55,6 @@ "settingType": "boolean", "defaultValue": true }, - { - "type": "setting", - "category": "Elements", - "order": 3, - "title": "Show rulers", - "settingName": "showMetricsRulers", - "settingType": "boolean", - "defaultValue": false - }, { "type": "setting", "category": "Elements", diff --git a/front_end/emulation/DeviceModeModel.js b/front_end/emulation/DeviceModeModel.js index 2a31e217b4..0aac6591b4 100644 --- a/front_end/emulation/DeviceModeModel.js +++ b/front_end/emulation/DeviceModeModel.js @@ -17,7 +17,8 @@ WebInspector.DeviceModeModel = function(updateCallback) this._initialized = false; this._deviceMetricsThrottler = new WebInspector.Throttler(0); this._appliedDeviceSize = new Size(1, 1); - this._currentDeviceScaleFactor = window.devicePixelRatio; + this._appliedDeviceScaleFactor = window.devicePixelRatio; + this._appliedUserAgentType = WebInspector.DeviceModeModel.UA.Desktop; this._scaleSetting = WebInspector.settings.createSetting("emulation.deviceScale", 1); // We've used to allow zero before. @@ -50,12 +51,6 @@ WebInspector.DeviceModeModel = function(updateCallback) this._device = null; /** @type {?WebInspector.EmulatedDevice.Mode} */ this._mode = null; - /** @type {boolean} */ - this._touchEnabled = false; - /** @type {string} */ - this._touchConfiguration = ""; - /** @type {string} */ - this._screenOrientation = ""; /** @type {number} */ this._fitScale = 1; @@ -75,9 +70,10 @@ WebInspector.DeviceModeModel.Type = { /** @enum {string} */ WebInspector.DeviceModeModel.UA = { - Mobile: "Mobile", - Desktop: "Desktop", - DesktopTouch: "DesktopTouch" + Mobile: WebInspector.UIString("Mobile"), + MobileNoTouch: WebInspector.UIString("Mobile (no touch)"), + Desktop: WebInspector.UIString("Desktop"), + DesktopTouch: WebInspector.UIString("Desktop (touch)") } WebInspector.DeviceModeModel.MinDeviceSize = 50; @@ -105,9 +101,8 @@ WebInspector.DeviceModeModel.deviceScaleFactorValidator = function(value) return false; } -WebInspector.DeviceModeModel._touchEventsScriptIdSymbol = Symbol("DeviceModeModel.touchEventsScriptIdSymbol"); WebInspector.DeviceModeModel._defaultMobileUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36"; -WebInspector.DeviceModeModel._defaultMobileScaleFactor = 2; +WebInspector.DeviceModeModel.defaultMobileScaleFactor = 2; WebInspector.DeviceModeModel.prototype = { /** @@ -272,6 +267,22 @@ WebInspector.DeviceModeModel.prototype = { return this._appliedDeviceSize; }, + /** + * @return {number} + */ + appliedDeviceScaleFactor: function() + { + return this._appliedDeviceScaleFactor; + }, + + /** + * @return {!WebInspector.DeviceModeModel.UA} + */ + appliedUserAgentType: function() + { + return this._appliedUserAgentType; + }, + /** * @return {boolean} */ @@ -304,19 +315,6 @@ WebInspector.DeviceModeModel.prototype = { return this._deviceScaleFactorSetting; }, - /** - * @return {number} - */ - defaultDeviceScaleFactor: function() - { - if (this._type === WebInspector.DeviceModeModel.Type.Responsive) - return this._uaSetting.get() === WebInspector.DeviceModeModel.UA.Mobile ? WebInspector.DeviceModeModel._defaultMobileScaleFactor : this._currentDeviceScaleFactor; - else if (this._type === WebInspector.DeviceModeModel.Type.Device) - return this._device.deviceScaleFactor; - else - return this._currentDeviceScaleFactor; - }, - reset: function() { this._deviceScaleFactorSetting.set(0); @@ -334,15 +332,10 @@ WebInspector.DeviceModeModel.prototype = { { if (!this._target) { this._target = target; - var domModel = WebInspector.DOMModel.fromTarget(this._target); - if (domModel) - domModel.addEventListener(WebInspector.DOMModel.Events.InspectModeWillBeToggled, this._reapplyTouch, this); if (this._onTargetAvailable) { var callback = this._onTargetAvailable; this._onTargetAvailable = null; callback(); - } else { - this._reapplyTouch(); } } }, @@ -353,12 +346,8 @@ WebInspector.DeviceModeModel.prototype = { */ targetRemoved: function(target) { - if (this._target === target) { - var domModel = WebInspector.DOMModel.fromTarget(this._target); - if (domModel) - domModel.removeEventListener(WebInspector.DOMModel.Events.InspectModeWillBeToggled, this._reapplyTouch, this); + if (this._target === target) this._target = null; - } }, _scaleSettingChanged: function() @@ -413,14 +402,19 @@ WebInspector.DeviceModeModel.prototype = { if (this._type === WebInspector.DeviceModeModel.Type.Device) { var orientation = this._device.orientationByName(this._mode.orientation); this._fitScale = this._calculateFitScale(orientation.width, orientation.height); - this._applyDeviceMetrics(new Size(orientation.width, orientation.height), this._mode.insets, this._scaleSetting.get(), this._device.deviceScaleFactor, this._device.mobile(), resetPageScaleFactor); + if (this._device.mobile()) + this._appliedUserAgentType = this._device.touch() ? WebInspector.DeviceModeModel.UA.Mobile : WebInspector.DeviceModeModel.UA.MobileNoTouch; + else + this._appliedUserAgentType = this._device.touch() ? WebInspector.DeviceModeModel.UA.DesktopTouch : WebInspector.DeviceModeModel.UA.Desktop; + this._applyDeviceMetrics(new Size(orientation.width, orientation.height), this._mode.insets, this._scaleSetting.get(), this._device.deviceScaleFactor, this._device.mobile(), this._mode.orientation == WebInspector.EmulatedDevice.Horizontal ? "landscapePrimary" : "portraitPrimary", resetPageScaleFactor); this._applyUserAgent(this._device.userAgent); - this._applyScreenOrientation(this._mode.orientation == WebInspector.EmulatedDevice.Horizontal ? "landscapePrimary" : "portraitPrimary"); + this._applyTouch(this._device.touch(), this._device.mobile()); } else if (this._type === WebInspector.DeviceModeModel.Type.None) { this._fitScale = this._calculateFitScale(this._availableSize.width, this._availableSize.height); - this._applyDeviceMetrics(this._availableSize, new Insets(0, 0, 0, 0), 1, 0, false, resetPageScaleFactor); + this._appliedUserAgentType = WebInspector.DeviceModeModel.UA.Desktop; + this._applyDeviceMetrics(this._availableSize, new Insets(0, 0, 0, 0), 1, 0, false, "", resetPageScaleFactor); this._applyUserAgent(""); - this._applyScreenOrientation(""); + this._applyTouch(false, false); } else if (this._type === WebInspector.DeviceModeModel.Type.Responsive) { var screenWidth = this._widthSetting.get(); if (!screenWidth || screenWidth > this._preferredScaledWidth()) @@ -428,14 +422,14 @@ WebInspector.DeviceModeModel.prototype = { var screenHeight = this._heightSetting.get(); if (!screenHeight || screenHeight > this._preferredScaledHeight()) screenHeight = this._preferredScaledHeight(); - var mobile = this._uaSetting.get() === WebInspector.DeviceModeModel.UA.Mobile; - var defaultDeviceScaleFactor = mobile ? WebInspector.DeviceModeModel._defaultMobileScaleFactor : 0; + var mobile = this._uaSetting.get() === WebInspector.DeviceModeModel.UA.Mobile || this._uaSetting.get() === WebInspector.DeviceModeModel.UA.MobileNoTouch; + var defaultDeviceScaleFactor = mobile ? WebInspector.DeviceModeModel.defaultMobileScaleFactor : 0; this._fitScale = this._calculateFitScale(this._widthSetting.get(), this._heightSetting.get()); - this._applyDeviceMetrics(new Size(screenWidth, screenHeight), new Insets(0, 0, 0, 0), this._scaleSetting.get(), this._deviceScaleFactorSetting.get() || defaultDeviceScaleFactor, mobile, resetPageScaleFactor); + this._appliedUserAgentType = this._uaSetting.get(); + this._applyDeviceMetrics(new Size(screenWidth, screenHeight), new Insets(0, 0, 0, 0), this._scaleSetting.get(), this._deviceScaleFactorSetting.get() || defaultDeviceScaleFactor, mobile, screenHeight >= screenWidth ? "portraitPrimary" : "landscapePrimary", resetPageScaleFactor); this._applyUserAgent(mobile ? WebInspector.DeviceModeModel._defaultMobileUserAgent : ""); - this._applyScreenOrientation(screenHeight >= screenWidth ? "portraitPrimary" : "landscapePrimary"); + this._applyTouch(this._uaSetting.get() === WebInspector.DeviceModeModel.UA.DesktopTouch || this._uaSetting.get() === WebInspector.DeviceModeModel.UA.Mobile, this._uaSetting.get() === WebInspector.DeviceModeModel.UA.Mobile); } - this._reapplyTouch(); this._updateCallback.call(null); }, @@ -460,23 +454,6 @@ WebInspector.DeviceModeModel.prototype = { this.setWidth(width); }, - _reapplyTouch: function() - { - var domModel = this._target ? WebInspector.DOMModel.fromTarget(this._target) : null; - var inspectModeEnabled = domModel ? domModel.inspectModeEnabled() : false; - if (inspectModeEnabled) { - this._applyTouch(false, false); - return; - } - - if (this._type === WebInspector.DeviceModeModel.Type.Device) - this._applyTouch(this._device.touch(), this._device.mobile()); - else if (this._type === WebInspector.DeviceModeModel.Type.None) - this._applyTouch(false, false); - else if (this._type === WebInspector.DeviceModeModel.Type.Responsive) - this._applyTouch(this._uaSetting.get() !== WebInspector.DeviceModeModel.UA.Desktop, this._uaSetting.get() === WebInspector.DeviceModeModel.UA.Mobile); - }, - /** * @param {string} userAgent */ @@ -491,9 +468,10 @@ WebInspector.DeviceModeModel.prototype = { * @param {number} scale * @param {number} deviceScaleFactor * @param {boolean} mobile + * @param {string} screenOrientation * @param {boolean} resetPageScaleFactor */ - _applyDeviceMetrics: function(screenSize, insets, scale, deviceScaleFactor, mobile, resetPageScaleFactor) + _applyDeviceMetrics: function(screenSize, insets, scale, deviceScaleFactor, mobile, screenOrientation, resetPageScaleFactor) { screenSize.width = Math.max(1, Math.floor(screenSize.width)); screenSize.height = Math.max(1, Math.floor(screenSize.height)); @@ -502,8 +480,10 @@ WebInspector.DeviceModeModel.prototype = { var pageHeight = screenSize.height - insets.top - insets.bottom; var positionX = insets.left; var positionY = insets.top; + var screenOrientationAngle = screenOrientation === "landscapePrimary" ? 90 : 0; this._appliedDeviceSize = screenSize; + this._appliedDeviceScaleFactor = deviceScaleFactor || window.devicePixelRatio; this._screenRect = new WebInspector.Rect( Math.max(0, (this._availableSize.width - screenSize.width * scale) / 2), 0, @@ -538,13 +518,19 @@ WebInspector.DeviceModeModel.prototype = { if (!this._target) return Promise.resolve(); - var clear = !pageWidth && !pageHeight && !mobile && !deviceScaleFactor && scale === 1; + var clear = !pageWidth && !pageHeight && !mobile && !deviceScaleFactor && scale === 1 && !screenOrientation; var allPromises = []; if (resetPageScaleFactor) allPromises.push(this._target.emulationAgent().resetPageScaleFactor()); - var setDevicePromise = clear ? - this._target.emulationAgent().clearDeviceMetricsOverride(this._deviceMetricsOverrideAppliedForTest.bind(this)) : - this._target.emulationAgent().setDeviceMetricsOverride(pageWidth, pageHeight, deviceScaleFactor, mobile, false, scale, 0, 0, screenSize.width, screenSize.height, positionX, positionY, this._deviceMetricsOverrideAppliedForTest.bind(this)); + var setDevicePromise; + if (clear) { + setDevicePromise = this._target.emulationAgent().clearDeviceMetricsOverride(this._deviceMetricsOverrideAppliedForTest.bind(this)); + } else { + var params = {width: pageWidth, height: pageHeight, deviceScaleFactor: deviceScaleFactor, mobile: mobile, fitWindow: false, scale: scale, screenWidth: screenSize.width, screenHeight: screenSize.height, positionX: positionX, positionY: positionY}; + if (screenOrientation) + params.screenOrientation = {type: screenOrientation, angle: screenOrientationAngle}; + setDevicePromise = this._target.emulationAgent().invoke_setDeviceMetricsOverride(params, this._deviceMetricsOverrideAppliedForTest.bind(this)); + } allPromises.push(setDevicePromise); return Promise.all(allPromises); } @@ -555,67 +541,12 @@ WebInspector.DeviceModeModel.prototype = { // Used for sniffing in tests. }, - _applyTouch: function(touchEnabled, mobile) - { - var configuration = mobile ? "mobile" : "desktop"; - if (!this._target || (this._touchEnabled === touchEnabled && this._touchConfiguration === configuration)) - return; - - var target = this._target; - - /** - * @suppressGlobalPropertiesCheck - */ - const injectedFunction = function() { - const touchEvents = ["ontouchstart", "ontouchend", "ontouchmove", "ontouchcancel"]; - var recepients = [window.__proto__, document.__proto__]; - for (var i = 0; i < touchEvents.length; ++i) { - for (var j = 0; j < recepients.length; ++j) { - if (!(touchEvents[i] in recepients[j])) - Object.defineProperty(recepients[j], touchEvents[i], { value: null, writable: true, configurable: true, enumerable: true }); - } - } - }; - - var symbol = WebInspector.DeviceModeModel._touchEventsScriptIdSymbol; - - if (typeof target[symbol] !== "undefined") { - target.pageAgent().removeScriptToEvaluateOnLoad(target[symbol]); - delete target[symbol]; - } - - if (touchEnabled) - target.pageAgent().addScriptToEvaluateOnLoad("(" + injectedFunction.toString() + ")()", scriptAddedCallback); - - /** - * @param {?Protocol.Error} error - * @param {string} scriptId - */ - function scriptAddedCallback(error, scriptId) - { - if (error) - delete target[symbol]; - else - target[symbol] = scriptId; - } - - target.emulationAgent().setTouchEmulationEnabled(touchEnabled, configuration); - this._touchEnabled = touchEnabled; - this._touchConfiguration = configuration; - }, - /** - * @param {string} orientation + * @param {boolean} touchEnabled + * @param {boolean} mobile */ - _applyScreenOrientation: function(orientation) + _applyTouch: function(touchEnabled, mobile) { - if (!this._target || orientation === this._screenOrientation) - return; - - this._screenOrientation = orientation; - if (!this._screenOrientation) - this._target.screenOrientationAgent().clearScreenOrientationOverride(); - else - this._target.screenOrientationAgent().setScreenOrientationOverride(this._screenOrientation === "landscapePrimary" ? 90 : 0, /** @type {!ScreenOrientationAgent.OrientationType} */ (this._screenOrientation)); + WebInspector.MultitargetTouchModel.instance().setTouchEnabled(touchEnabled, mobile); } } diff --git a/front_end/emulation/DeviceModeToolbar.js b/front_end/emulation/DeviceModeToolbar.js index a71ce0b27a..8f53f3ae4a 100644 --- a/front_end/emulation/DeviceModeToolbar.js +++ b/front_end/emulation/DeviceModeToolbar.js @@ -13,6 +13,16 @@ WebInspector.DeviceModeToolbar = function(model, showMediaInspectorSetting, show this._model = model; this._showMediaInspectorSetting = showMediaInspectorSetting; this._showRulersSetting = showRulersSetting; + + this._showDeviceScaleFactorSetting = WebInspector.settings.createSetting("emulation.showDeviceScaleFactor", false); + this._showDeviceScaleFactorSetting.addChangeListener(this._updateDeviceScaleFactorVisibility, this); + + this._showUserAgentTypeSetting = WebInspector.settings.createSetting("emulation.showUserAgentType", false); + this._showUserAgentTypeSetting.addChangeListener(this._updateUserAgentTypeVisibility, this); + + this._showNetworkConditionsSetting = WebInspector.settings.createSetting("emulation.showNetworkConditions", false); + this._showNetworkConditionsSetting.addChangeListener(this._updateNetworkConditionsVisibility, this); + /** @type {!Map} */ this._lastMode = new Map(); @@ -129,6 +139,24 @@ WebInspector.DeviceModeToolbar.prototype = { this._scaleItem.setGlyph(""); this._scaleItem.turnIntoSelect(); toolbar.appendToolbarItem(this._scaleItem); + + toolbar.appendToolbarItem(this._wrapToolbarItem(createElementWithClass("div", "device-mode-empty-toolbar-element"))); + this._deviceScaleItem = new WebInspector.ToolbarMenuButton(this._appendDeviceScaleMenuItems.bind(this)); + this._deviceScaleItem.setVisible(this._showDeviceScaleFactorSetting.get()); + this._deviceScaleItem.setTitle(WebInspector.UIString("Device pixel ratio")); + this._deviceScaleItem.setGlyph(""); + this._deviceScaleItem.turnIntoSelect(); + this._deviceScaleItem.element.style.padding = "0 5px"; + toolbar.appendToolbarItem(this._deviceScaleItem); + + toolbar.appendToolbarItem(this._wrapToolbarItem(createElementWithClass("div", "device-mode-empty-toolbar-element"))); + this._uaItem = new WebInspector.ToolbarMenuButton(this._appendUserAgentMenuItems.bind(this)); + this._uaItem.setVisible(this._showUserAgentTypeSetting.get()); + this._uaItem.setTitle(WebInspector.UIString("Device type")); + this._uaItem.setGlyph(""); + this._uaItem.turnIntoSelect(); + this._uaItem.element.style.padding = "0 5px"; + toolbar.appendToolbarItem(this._uaItem); }, /** @@ -147,17 +175,12 @@ WebInspector.DeviceModeToolbar.prototype = { */ _fillOptionsToolbar: function(toolbar) { - this._uaItem = new WebInspector.ToolbarText(); - this._uaItem.setVisible(false); - this._uaItem.setTitle(WebInspector.UIString("User agent type")); - this._uaItem.element.style.opacity = "0.5"; - toolbar.appendToolbarItem(this._uaItem); - - this._deviceScaleItem = new WebInspector.ToolbarText(); - this._deviceScaleItem.setVisible(false); - this._deviceScaleItem.setTitle(WebInspector.UIString("Device pixel ratio")); - this._deviceScaleItem.element.style.opacity = "0.5"; - toolbar.appendToolbarItem(this._deviceScaleItem); + this._networkConditionsItem = WebInspector.NetworkConditionsSelector.createToolbarMenuButton(); + this._networkConditionsItem.setVisible(this._showNetworkConditionsSetting.get()); + this._networkConditionsItem.setTitle(WebInspector.UIString("Network throttling")); + this._networkConditionsItem.element.style.padding = "0 5px"; + this._networkConditionsItem.element.style.maxWidth = "140px"; + toolbar.appendToolbarItem(this._networkConditionsItem); var moreOptionsButton = new WebInspector.ToolbarMenuButton(this._appendOptionsMenuItems.bind(this)); moreOptionsButton.setTitle(WebInspector.UIString("More options")); @@ -196,35 +219,12 @@ WebInspector.DeviceModeToolbar.prototype = { /** * @param {!WebInspector.ContextMenu} contextMenu */ - _appendOptionsMenuItems: function(contextMenu) + _appendDeviceScaleMenuItems: function(contextMenu) { - var uaDisabled = this._model.type() !== WebInspector.DeviceModeModel.Type.Responsive; - var uaSetting = this._model.uaSetting(); - var uaSubmenu = contextMenu.appendSubMenuItem(WebInspector.UIString("User agent type"), false); - var uaValue = this._model.uaSetting().get(); - if (this._model.type() === WebInspector.DeviceModeModel.Type.None) - uaValue = WebInspector.DeviceModeModel.UA.Desktop; - if (this._model.type() === WebInspector.DeviceModeModel.Type.Device) - uaValue = this._model.device().mobile() ? WebInspector.DeviceModeModel.UA.Mobile : this._model.device().touch() ? WebInspector.DeviceModeModel.UA.DesktopTouch : WebInspector.DeviceModeModel.UA.Desktop; - appendUAItem(WebInspector.UIString("Mobile (default)"), WebInspector.DeviceModeModel.UA.Mobile); - appendUAItem(WebInspector.UIString("Desktop"), WebInspector.DeviceModeModel.UA.Desktop); - appendUAItem(WebInspector.UIString("Desktop with touch"), WebInspector.DeviceModeModel.UA.DesktopTouch); - - /** - * @param {string} title - * @param {!WebInspector.DeviceModeModel.UA} value - */ - function appendUAItem(title, value) - { - uaSubmenu.appendCheckboxItem(title, uaSetting.set.bind(uaSetting, value), uaValue === value, uaDisabled); - } - - var deviceScaleFactorDisabled = this._model.type() !== WebInspector.DeviceModeModel.Type.Responsive; - var deviceScaleFactorSubmenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Device pixel ratio"), false); var deviceScaleFactorSetting = this._model.deviceScaleFactorSetting(); - var deviceScaleFactorValue = deviceScaleFactorDisabled ? 0 : deviceScaleFactorSetting.get(); - appendDeviceScaleFactorItem(WebInspector.UIString("Default: %f", this._model.defaultDeviceScaleFactor()), 0); - deviceScaleFactorSubmenu.appendSeparator(); + var defaultValue = this._model.uaSetting().get() === WebInspector.DeviceModeModel.UA.Mobile || this._model.uaSetting().get() === WebInspector.DeviceModeModel.UA.MobileNoTouch ? WebInspector.DeviceModeModel.defaultMobileScaleFactor : window.devicePixelRatio; + appendDeviceScaleFactorItem(WebInspector.UIString("Default: %.1f", defaultValue), 0); + contextMenu.appendSeparator(); appendDeviceScaleFactorItem(WebInspector.UIString("1"), 1); appendDeviceScaleFactorItem(WebInspector.UIString("2"), 2); appendDeviceScaleFactorItem(WebInspector.UIString("3"), 3); @@ -235,16 +235,55 @@ WebInspector.DeviceModeToolbar.prototype = { */ function appendDeviceScaleFactorItem(title, value) { - deviceScaleFactorSubmenu.appendCheckboxItem(title, deviceScaleFactorSetting.set.bind(deviceScaleFactorSetting, value), deviceScaleFactorValue === value, deviceScaleFactorDisabled); + contextMenu.appendCheckboxItem(title, deviceScaleFactorSetting.set.bind(deviceScaleFactorSetting, value), deviceScaleFactorSetting.get() === value); } + }, - contextMenu.appendItem(WebInspector.UIString("Reset to defaults"), this._model.reset.bind(this._model), this._model.type() !== WebInspector.DeviceModeModel.Type.Responsive); - contextMenu.appendSeparator(); + /** + * @param {!WebInspector.ContextMenu} contextMenu + */ + _appendUserAgentMenuItems: function(contextMenu) + { + var uaSetting = this._model.uaSetting(); + appendUAItem(WebInspector.DeviceModeModel.UA.Mobile, WebInspector.DeviceModeModel.UA.Mobile); + appendUAItem(WebInspector.DeviceModeModel.UA.MobileNoTouch, WebInspector.DeviceModeModel.UA.MobileNoTouch); + appendUAItem(WebInspector.DeviceModeModel.UA.Desktop, WebInspector.DeviceModeModel.UA.Desktop); + appendUAItem(WebInspector.DeviceModeModel.UA.DesktopTouch, WebInspector.DeviceModeModel.UA.DesktopTouch); + /** + * @param {string} title + * @param {!WebInspector.DeviceModeModel.UA} value + */ + function appendUAItem(title, value) + { + contextMenu.appendCheckboxItem(title, uaSetting.set.bind(uaSetting, value), uaSetting.get() === value); + } + }, + + /** + * @param {!WebInspector.ContextMenu} contextMenu + */ + _appendOptionsMenuItems: function(contextMenu) + { + contextMenu.appendCheckboxItem(WebInspector.UIString("Show device pixel ratio"), this._toggleDeviceScaleFactor.bind(this), this._showDeviceScaleFactorSetting.get(), this._model.type() === WebInspector.DeviceModeModel.Type.None); + contextMenu.appendCheckboxItem(WebInspector.UIString("Show device type"), this._toggleUserAgentType.bind(this), this._showUserAgentTypeSetting.get(), this._model.type() === WebInspector.DeviceModeModel.Type.None); + contextMenu.appendCheckboxItem(WebInspector.UIString("Show throttling"), this._toggleNetworkConditions.bind(this), this._showNetworkConditionsSetting.get(), this._model.type() === WebInspector.DeviceModeModel.Type.None); contextMenu.appendCheckboxItem(WebInspector.UIString("Show media queries"), this._toggleMediaInspector.bind(this), this._showMediaInspectorSetting.get(), this._model.type() === WebInspector.DeviceModeModel.Type.None); contextMenu.appendCheckboxItem(WebInspector.UIString("Show rulers"), this._toggleRulers.bind(this), this._showRulersSetting.get(), this._model.type() === WebInspector.DeviceModeModel.Type.None); - contextMenu.appendItem(WebInspector.UIString("Configure network\u2026"), this._openNetworkConfig.bind(this), false); + contextMenu.appendSeparator(); contextMenu.appendItemsAtLocation("deviceModeMenu"); + contextMenu.appendSeparator(); + contextMenu.appendItem(WebInspector.UIString("Reset to defaults"), this._reset.bind(this)); + }, + + _toggleDeviceScaleFactor: function() + { + this._showDeviceScaleFactorSetting.set(!this._showDeviceScaleFactorSetting.get()); + }, + + _toggleUserAgentType: function() + { + this._showUserAgentTypeSetting.set(!this._showUserAgentTypeSetting.get()); }, _toggleMediaInspector: function() @@ -257,11 +296,19 @@ WebInspector.DeviceModeToolbar.prototype = { this._showRulersSetting.set(!this._showRulersSetting.get()); }, - _openNetworkConfig: function() + _toggleNetworkConditions: function() { - InspectorFrontendHost.bringToFront(); - // TODO(dgozman): make it explicit. - WebInspector.actionRegistry.action("network.show-config").execute(); + this._showNetworkConditionsSetting.set(!this._showNetworkConditionsSetting.get()); + }, + + _reset: function() + { + this._showDeviceScaleFactorSetting.set(false); + this._showUserAgentTypeSetting.set(false); + this._showMediaInspectorSetting.set(false); + this._showRulersSetting.set(false); + this._showNetworkConditionsSetting.set(false); + this._model.reset(); }, /** @@ -366,6 +413,21 @@ WebInspector.DeviceModeToolbar.prototype = { } }, + _updateDeviceScaleFactorVisibility: function() + { + this._deviceScaleItem.setVisible(this._showDeviceScaleFactorSetting.get()); + }, + + _updateUserAgentTypeVisibility: function() + { + this._uaItem.setVisible(this._showUserAgentTypeSetting.get()); + }, + + _updateNetworkConditionsVisibility: function() + { + this._networkConditionsItem.setVisible(this._showNetworkConditionsSetting.get()); + }, + /** * @param {!WebInspector.Event} event */ @@ -436,6 +498,8 @@ WebInspector.DeviceModeToolbar.prototype = { this._cachedModelType = this._model.type(); this._widthInput.disabled = this._model.type() !== WebInspector.DeviceModeModel.Type.Responsive; this._heightInput.disabled = this._model.type() !== WebInspector.DeviceModeModel.Type.Responsive; + this._deviceScaleItem.setEnabled(this._model.type() === WebInspector.DeviceModeModel.Type.Responsive); + this._uaItem.setEnabled(this._model.type() === WebInspector.DeviceModeModel.Type.Responsive); } var size = this._model.appliedDeviceSize(); @@ -445,21 +509,18 @@ WebInspector.DeviceModeToolbar.prototype = { if (this._model.scale() !== this._cachedScale) { this._scaleItem.setText(WebInspector.UIString("%.0f%%", this._model.scale() * 100)); - this._scaleItem.setState(this._model.scale() === 1 ? "off" : "on"); this._cachedScale = this._model.scale(); } - var deviceScale = this._model.deviceScaleFactorSetting().get(); - this._deviceScaleItem.setVisible(this._model.type() === WebInspector.DeviceModeModel.Type.Responsive && !!deviceScale); + var deviceScale = this._model.appliedDeviceScaleFactor(); if (deviceScale !== this._cachedDeviceScale) { this._deviceScaleItem.setText(WebInspector.UIString("DPR: %.1f", deviceScale)); this._cachedDeviceScale = deviceScale; } - var uaType = this._model.type() === WebInspector.DeviceModeModel.Type.Responsive ? this._model.uaSetting().get() : WebInspector.DeviceModeModel.UA.Mobile; - this._uaItem.setVisible(this._model.type() === WebInspector.DeviceModeModel.Type.Responsive && uaType !== WebInspector.DeviceModeModel.UA.Mobile); + var uaType = this._model.appliedUserAgentType(); if (uaType !== this._cachedUaType) { - this._uaItem.setText(uaType === WebInspector.DeviceModeModel.UA.Desktop ? WebInspector.UIString("Desktop") : WebInspector.UIString("Touch")); + this._uaItem.setText(uaType); this._cachedUaType = uaType; } diff --git a/front_end/emulation/DeviceModeView.js b/front_end/emulation/DeviceModeView.js index 93f63389e7..d4c2a4969a 100644 --- a/front_end/emulation/DeviceModeView.js +++ b/front_end/emulation/DeviceModeView.js @@ -265,7 +265,7 @@ WebInspector.DeviceModeView.prototype = { this._toolbar.update(); this._loadScreenImage(this._model.screenImage()); - this._mediaInspector.setAxisTransform(-(cssScreenRect.left + cssScreenRect.width / 2) * zoomFactor * 2 / this._model.scale(), this._model.scale() * 0.5); + this._mediaInspector.setAxisTransform(this._model.scale()); if (callDoResize) this.doResize(); if (updateRulers) { diff --git a/front_end/emulation/DevicesSettingsTab.js b/front_end/emulation/DevicesSettingsTab.js index cc48893547..2df06bff52 100644 --- a/front_end/emulation/DevicesSettingsTab.js +++ b/front_end/emulation/DevicesSettingsTab.js @@ -154,7 +154,12 @@ WebInspector.DevicesSettingsTab.prototype = { device.modes = []; device.modes.push({title: "", orientation: WebInspector.EmulatedDevice.Vertical, insets: new Insets(0, 0, 0, 0), images: null}); device.modes.push({title: "", orientation: WebInspector.EmulatedDevice.Horizontal, insets: new Insets(0, 0, 0, 0), images: null}); - + device.capabilities = []; + var uaType = editor.control("ua-type").value; + if (uaType === WebInspector.DeviceModeModel.UA.Mobile || uaType === WebInspector.DeviceModeModel.UA.MobileNoTouch) + device.capabilities.push(WebInspector.EmulatedDevice.Capability.Mobile); + if (uaType === WebInspector.DeviceModeModel.UA.Mobile || uaType === WebInspector.DeviceModeModel.UA.DesktopTouch) + device.capabilities.push(WebInspector.EmulatedDevice.Capability.Touch); if (isNew) this._emulatedDevicesList.addCustomDevice(device); else @@ -177,6 +182,12 @@ WebInspector.DevicesSettingsTab.prototype = { editor.control("height").value = this._toNumericInputValue(device.vertical.height); editor.control("scale").value = this._toNumericInputValue(device.deviceScaleFactor); editor.control("user-agent").value = device.userAgent; + var uaType; + if (device.mobile()) + uaType = device.touch() ? WebInspector.DeviceModeModel.UA.Mobile : WebInspector.DeviceModeModel.UA.MobileNoTouch; + else + uaType = device.touch() ? WebInspector.DeviceModeModel.UA.DesktopTouch : WebInspector.DeviceModeModel.UA.Desktop; + editor.control("ua-type").value = uaType; return editor; }, @@ -193,16 +204,18 @@ WebInspector.DevicesSettingsTab.prototype = { var content = editor.contentElement(); var fields = content.createChild("div", "devices-edit-fields"); - fields.appendChild(editor.createInput("title", "text", WebInspector.UIString("Device name"), titleValidator)); + fields.createChild("div", "hbox").appendChild(editor.createInput("title", "text", WebInspector.UIString("Device name"), titleValidator)); var screen = fields.createChild("div", "hbox"); - var width = editor.createInput("width", "text", WebInspector.UIString("Width"), sizeValidator); - width.classList.add("device-edit-small"); - screen.appendChild(width); - var height = editor.createInput("height", "text", WebInspector.UIString("height"), sizeValidator); - height.classList.add("device-edit-small"); - screen.appendChild(height); - screen.appendChild(editor.createInput("scale", "text", WebInspector.UIString("Device pixel ratio"), scaleValidator)); - fields.appendChild(editor.createInput("user-agent", "text", WebInspector.UIString("User agent string"), userAgentValidator)); + screen.appendChild(editor.createInput("width", "text", WebInspector.UIString("Width"), sizeValidator)); + screen.appendChild(editor.createInput("height", "text", WebInspector.UIString("height"), sizeValidator)); + var dpr = editor.createInput("scale", "text", WebInspector.UIString("Device pixel ratio"), scaleValidator); + dpr.classList.add("device-edit-fixed"); + screen.appendChild(dpr); + var ua = fields.createChild("div", "hbox"); + ua.appendChild(editor.createInput("user-agent", "text", WebInspector.UIString("User agent string"), () => true)); + var uaType = editor.createSelect("ua-type", [WebInspector.DeviceModeModel.UA.Mobile, WebInspector.DeviceModeModel.UA.MobileNoTouch, WebInspector.DeviceModeModel.UA.Desktop, WebInspector.DeviceModeModel.UA.DesktopTouch], () => true); + uaType.classList.add("device-edit-fixed"); + ua.appendChild(uaType); return editor; @@ -239,17 +252,6 @@ WebInspector.DevicesSettingsTab.prototype = { { return WebInspector.DeviceModeModel.deviceScaleFactorValidator(input.value); } - - /** - * @param {*} item - * @param {number} index - * @param {!HTMLInputElement|!HTMLSelectElement} input - * @return {boolean} - */ - function userAgentValidator(item, index, input) - { - return true; - } }, __proto__: WebInspector.VBox.prototype diff --git a/front_end/emulation/MediaQueryInspector.js b/front_end/emulation/MediaQueryInspector.js index 198b67ba7c..a386051abe 100644 --- a/front_end/emulation/MediaQueryInspector.js +++ b/front_end/emulation/MediaQueryInspector.js @@ -20,7 +20,6 @@ WebInspector.MediaQueryInspector = function(getWidthCallback, setWidthCallback) this._getWidthCallback = getWidthCallback; this._setWidthCallback = setWidthCallback; - this._offset = 0; this._scale = 1; WebInspector.targetManager.observeTargets(this); @@ -71,14 +70,12 @@ WebInspector.MediaQueryInspector.prototype = { }, /** - * @param {number} offset * @param {number} scale */ - setAxisTransform: function(offset, scale) + setAxisTransform: function(scale) { - if (this._offset === offset && Math.abs(this._scale - scale) < 1e-8) + if (Math.abs(this._scale - scale) < 1e-8) return; - this._offset = offset; this._scale = scale; this._renderMediaQueries(); }, @@ -88,7 +85,7 @@ WebInspector.MediaQueryInspector.prototype = { */ _onMediaQueryClicked: function(event) { - var mediaQueryMarker = event.target.enclosingNodeOrSelfWithClass("media-inspector-marker"); + var mediaQueryMarker = event.target.enclosingNodeOrSelfWithClass("media-inspector-bar"); if (!mediaQueryMarker) return; @@ -116,7 +113,7 @@ WebInspector.MediaQueryInspector.prototype = { if (!this._cssModel || !this._cssModel.isEnabled()) return; - var mediaQueryMarker = event.target.enclosingNodeOrSelfWithClass("media-inspector-marker"); + var mediaQueryMarker = event.target.enclosingNodeOrSelfWithClass("media-inspector-bar"); if (!mediaQueryMarker) return; @@ -275,41 +272,54 @@ WebInspector.MediaQueryInspector.prototype = { _createElementFromMediaQueryModel: function(model) { var zoomFactor = this._zoomFactor(); - var minWidthValue = model.minWidthExpression() ? model.minWidthExpression().computedLength() : 0; - - const styleClassPerSection = [ - "media-inspector-marker-max-width", - "media-inspector-marker-min-max-width", - "media-inspector-marker-min-width" - ]; - var markerElement = createElementWithClass("div", "media-inspector-marker"); - var leftPixelValue = minWidthValue ? (minWidthValue - this._offset) / zoomFactor : 0; - markerElement.style.left = leftPixelValue + "px"; - markerElement.classList.add(styleClassPerSection[model.section()]); - var widthPixelValue = null; - if (model.maxWidthExpression() && model.minWidthExpression()) - widthPixelValue = (model.maxWidthExpression().computedLength() - minWidthValue) / zoomFactor; - else if (model.maxWidthExpression()) - widthPixelValue = (model.maxWidthExpression().computedLength() - this._offset) / zoomFactor; - else - markerElement.style.right = "0"; - if (typeof widthPixelValue === "number") - markerElement.style.width = widthPixelValue + "px"; - - if (model.minWidthExpression()) { - var labelClass = model.section() === WebInspector.MediaQueryInspector.Section.MinMax ? "media-inspector-label-right" : "media-inspector-label-left"; - var labelContainer = markerElement.createChild("div", "media-inspector-marker-label-container media-inspector-marker-label-container-left"); - labelContainer.createChild("span", "media-inspector-marker-label " + labelClass).textContent = model.minWidthExpression().value() + model.minWidthExpression().unit(); + var minWidthValue = model.minWidthExpression() ? model.minWidthExpression().computedLength() / zoomFactor : 0; + var maxWidthValue = model.maxWidthExpression() ? model.maxWidthExpression().computedLength() / zoomFactor : 0; + var result = createElementWithClass("div", "media-inspector-bar"); + + if (model.section() === WebInspector.MediaQueryInspector.Section.Max) { + result.createChild("div", "media-inspector-marker-spacer"); + var markerElement = result.createChild("div", "media-inspector-marker media-inspector-marker-max-width"); + markerElement.style.width = maxWidthValue + "px"; + markerElement.title = model.mediaText(); + appendLabel(markerElement, model.maxWidthExpression(), false, false); + appendLabel(markerElement, model.maxWidthExpression(), true, true); + result.createChild("div", "media-inspector-marker-spacer"); + } + + if (model.section() === WebInspector.MediaQueryInspector.Section.MinMax) { + result.createChild("div", "media-inspector-marker-spacer"); + var leftElement = result.createChild("div", "media-inspector-marker media-inspector-marker-min-max-width"); + leftElement.style.width = (maxWidthValue - minWidthValue) * 0.5 + "px"; + leftElement.title = model.mediaText(); + appendLabel(leftElement, model.minWidthExpression(), true, false); + appendLabel(leftElement, model.maxWidthExpression(), false, true); + result.createChild("div", "media-inspector-marker-spacer").style.flex = "0 0 " + minWidthValue + "px"; + var rightElement = result.createChild("div", "media-inspector-marker media-inspector-marker-min-max-width"); + rightElement.style.width = (maxWidthValue - minWidthValue) * 0.5 + "px"; + rightElement.title = model.mediaText(); + appendLabel(rightElement, model.minWidthExpression(), true, false); + appendLabel(rightElement, model.maxWidthExpression(), false, true); + result.createChild("div", "media-inspector-marker-spacer"); } - if (model.maxWidthExpression()) { - var labelClass = model.section() === WebInspector.MediaQueryInspector.Section.MinMax ? "media-inspector-label-left" : "media-inspector-label-right"; - var labelContainer = markerElement.createChild("div", "media-inspector-marker-label-container media-inspector-marker-label-container-right"); - labelContainer.createChild("span", "media-inspector-marker-label " + labelClass).textContent = model.maxWidthExpression().value() + model.maxWidthExpression().unit(); + if (model.section() === WebInspector.MediaQueryInspector.Section.Min) { + var leftElement = result.createChild("div", "media-inspector-marker media-inspector-marker-min-width media-inspector-marker-min-width-left"); + leftElement.title = model.mediaText(); + appendLabel(leftElement, model.minWidthExpression(), false, false); + result.createChild("div", "media-inspector-marker-spacer").style.flex = "0 0 " + minWidthValue + "px"; + var rightElement = result.createChild("div", "media-inspector-marker media-inspector-marker-min-width media-inspector-marker-min-width-right"); + rightElement.title = model.mediaText(); + appendLabel(rightElement, model.minWidthExpression(), true, true); + } + + function appendLabel(marker, expression, atLeft, leftAlign) + { + marker.createChild("div", "media-inspector-marker-label-container " + (atLeft ? "media-inspector-marker-label-container-left" : "media-inspector-marker-label-container-right")) + .createChild("span", "media-inspector-marker-label " + (leftAlign ? "media-inspector-label-left" : "media-inspector-label-right")) + .textContent = expression.value() + expression.unit(); } - markerElement.title = model.mediaText(); - return markerElement; + return result; }, __proto__: WebInspector.Widget.prototype diff --git a/front_end/emulation/SensorsView.js b/front_end/emulation/SensorsView.js index fb30739325..d9cf4d773f 100644 --- a/front_end/emulation/SensorsView.js +++ b/front_end/emulation/SensorsView.js @@ -21,6 +21,8 @@ WebInspector.SensorsView = function() this._deviceOrientation = WebInspector.DeviceOrientation.parseSetting(this._deviceOrientationSetting.get()); this._deviceOrientationEnabled = false; this._appendDeviceOrientationOverrideControl(); + + this._appendTouchControl(); } WebInspector.SensorsView.prototype = { @@ -297,6 +299,21 @@ WebInspector.SensorsView.prototype = { return new WebInspector.Geometry.Vector(sphereX, sphereY, Math.sqrt(1 - sqrSum)); }, + _appendTouchControl: function() + { + var label = this.contentElement.createChild("label", "touch-label"); + label.createChild("span", "").textContent = WebInspector.UIString("Touch"); + var select = label.createChild("select", "chrome-select"); + select.appendChild(new Option(WebInspector.UIString("Device-based"), "auto")); + select.appendChild(new Option(WebInspector.UIString("Force enabled"), "enabled")); + select.addEventListener("change", applyTouch, false); + + function applyTouch() + { + WebInspector.MultitargetTouchModel.instance().setCustomTouchEnabled(select.value === "enabled"); + } + }, + __proto__ : WebInspector.VBox.prototype } diff --git a/front_end/emulation/TouchModel.js b/front_end/emulation/TouchModel.js new file mode 100644 index 0000000000..d0a39d7f48 --- /dev/null +++ b/front_end/emulation/TouchModel.js @@ -0,0 +1,149 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + * @implements {WebInspector.TargetManager.Observer} + */ +WebInspector.MultitargetTouchModel = function() +{ + this._touchEnabled = false; + this._touchMobile = false; + this._customTouchEnabled = false; + + WebInspector.targetManager.observeTargets(this, WebInspector.Target.Type.Page); +} + +WebInspector.MultitargetTouchModel._symbol = Symbol("MultitargetTouchModel.symbol"); + +WebInspector.MultitargetTouchModel.prototype = { + /** + * @param {boolean} enabled + * @param {boolean} mobile + */ + setTouchEnabled: function(enabled, mobile) + { + this._touchEnabled = enabled; + this._touchMobile = mobile; + this._updateAllTargets(); + }, + + /** + * @param {boolean} enabled + */ + setCustomTouchEnabled: function(enabled) + { + this._customTouchEnabled = enabled; + this._updateAllTargets(); + }, + + _updateAllTargets: function() + { + for (var target of WebInspector.targetManager.targets(WebInspector.Target.Type.Page)) + this._applyToTarget(target); + }, + + /** + * @param {!WebInspector.Target} target + */ + _applyToTarget: function(target) + { + var current = {enabled: this._touchEnabled, configuration : this._touchMobile ? "mobile" : "desktop"}; + if (this._customTouchEnabled) + current = {enabled: true, configuration: "mobile"}; + + var domModel = WebInspector.DOMModel.fromTarget(target); + var inspectModeEnabled = domModel ? domModel.inspectModeEnabled() : false; + if (inspectModeEnabled) + current = {enabled: false, configuration: "mobile"}; + + /** + * @suppressGlobalPropertiesCheck + */ + const injectedFunction = function() { + const touchEvents = ["ontouchstart", "ontouchend", "ontouchmove", "ontouchcancel"]; + var recepients = [window.__proto__, document.__proto__]; + for (var i = 0; i < touchEvents.length; ++i) { + for (var j = 0; j < recepients.length; ++j) { + if (!(touchEvents[i] in recepients[j])) + Object.defineProperty(recepients[j], touchEvents[i], { value: null, writable: true, configurable: true, enumerable: true }); + } + } + }; + + var symbol = WebInspector.MultitargetTouchModel._symbol; + var previous = target[symbol] || {enabled: false, configuration: "mobile", scriptId: ""}; + + if (previous.enabled === current.enabled && (!current.enabled || previous.configuration === current.configuration)) + return; + + if (previous.scriptId) { + target.pageAgent().removeScriptToEvaluateOnLoad(previous.scriptId); + target[symbol].scriptId = ""; + } + + target[symbol] = current; + target[symbol].scriptId = ""; + + if (current.enabled) + target.pageAgent().addScriptToEvaluateOnLoad("(" + injectedFunction.toString() + ")()", scriptAddedCallback); + + /** + * @param {?Protocol.Error} error + * @param {string} scriptId + */ + function scriptAddedCallback(error, scriptId) + { + (target[symbol] || {}).scriptId = error ? "" : scriptId; + } + + target.emulationAgent().setTouchEmulationEnabled(current.enabled, current.configuration); + }, + + /** + * @param {!WebInspector.Event} event + */ + _inspectModeToggled: function(event) + { + var domModel = /** @type {!WebInspector.DOMModel} */ (event.target); + this._applyToTarget(domModel.target()); + }, + + /** + * @override + * @param {!WebInspector.Target} target + */ + targetAdded: function(target) + { + var domModel = WebInspector.DOMModel.fromTarget(target); + if (domModel) + domModel.addEventListener(WebInspector.DOMModel.Events.InspectModeWillBeToggled, this._inspectModeToggled, this); + this._applyToTarget(target); + }, + + /** + * @override + * @param {!WebInspector.Target} target + */ + targetRemoved: function(target) + { + var domModel = WebInspector.DOMModel.fromTarget(target); + if (domModel) + domModel.removeEventListener(WebInspector.DOMModel.Events.InspectModeWillBeToggled, this._inspectModeToggled, this); + } +} + + +/** @type {?WebInspector.MultitargetTouchModel} */ +WebInspector.MultitargetTouchModel._instance = null; + +/** + * @return {!WebInspector.MultitargetTouchModel} + */ +WebInspector.MultitargetTouchModel.instance = function() +{ + if (!WebInspector.MultitargetTouchModel._instance) + WebInspector.MultitargetTouchModel._instance = new WebInspector.MultitargetTouchModel(); + return /** @type {!WebInspector.MultitargetTouchModel} */ (WebInspector.MultitargetTouchModel._instance); +} diff --git a/front_end/emulation/devicesSettingsTab.css b/front_end/emulation/devicesSettingsTab.css index eb3cf1d755..a731ee9f96 100644 --- a/front_end/emulation/devicesSettingsTab.css +++ b/front_end/emulation/devicesSettingsTab.css @@ -78,6 +78,10 @@ padding: 3px; } -.devices-edit-fields input.device-edit-small { - flex: 0 0 80px; +.devices-edit-fields .device-edit-fixed { + flex: 0 0 140px; +} + +.devices-edit-fields select { + margin: 8px 5px 0 5px; } diff --git a/front_end/emulation/mediaQueryInspector.css b/front_end/emulation/mediaQueryInspector.css index 5619a8ecca..7e96b7ca95 100644 --- a/front_end/emulation/mediaQueryInspector.css +++ b/front_end/emulation/mediaQueryInspector.css @@ -11,31 +11,48 @@ } .media-inspector-marker-container { - position: relative; height: 14px; margin: 2px 0; + position: relative; } -.media-inspector-marker { +.media-inspector-bar { + display: flex; + flex-direction: row; + align-items: stretch; + pointer-events: none; position: absolute; - top: 1px; - bottom: 1px; + left: 0; + right: 0; + top: 0; + bottom: 0; +} + +.media-inspector-marker { + flex: none; + pointer-events: auto; + margin: 1px 0; white-space: nowrap; z-index: auto; + position: relative; +} + +.media-inspector-marker-spacer { + flex: auto; } .media-inspector-marker:hover { - top: -1px; - bottom: -1px; + margin: -1px 0; opacity: 1; } .media-inspector-marker-max-width { background-color: hsl(207, 90%, 77%); border-right: 2px solid hsl(207, 90%, 61%); + border-left: 2px solid hsl(207, 90%, 61%); } -.media-inspector-marker-max-width.media-inspector-marker-inactive:not(:hover) { +.media-inspector-marker-inactive .media-inspector-marker-max-width:not(:hover) { background-color: hsl(199, 94%, 94%); } @@ -45,7 +62,7 @@ border-right: 2px solid hsl(92, 48%, 42%); } -.media-inspector-marker-min-max-width.media-inspector-marker-inactive:not(:hover) { +.media-inspector-marker-inactive .media-inspector-marker-min-max-width:not(:hover) { background-color: hsl(125, 39%, 94%); } @@ -55,13 +72,21 @@ .media-inspector-marker-min-width { background-color: hsl(36, 100%, 75%); - border-left: 2px solid hsl(30, 100%, 48%); + flex: auto; } -.media-inspector-marker-min-width.media-inspector-marker-inactive:not(:hover) { +.media-inspector-marker-inactive .media-inspector-marker-min-width:not(:hover) { background-color: hsl(37, 100%, 94%); } +.media-inspector-marker-min-width-right { + border-left: 2px solid hsl(30, 100%, 48%); +} + +.media-inspector-marker-min-width-left { + border-right: 2px solid hsl(30, 100%, 48%); +} + /* Media query labels */ .media-inspector-marker:not(:hover) .media-inspector-marker-label-container { diff --git a/front_end/emulation/module.json b/front_end/emulation/module.json index 834e7fb44a..9e76b70050 100644 --- a/front_end/emulation/module.json +++ b/front_end/emulation/module.json @@ -91,6 +91,7 @@ "InspectedPagePlaceholder.js", "MediaQueryInspector.js", "SensorsView.js", + "TouchModel.js", "DeviceModeModel.js", "DeviceModeToolbar.js", "DeviceModeView.js", diff --git a/front_end/emulation/sensors.css b/front_end/emulation/sensors.css index 919e27794d..7cb82460ae 100644 --- a/front_end/emulation/sensors.css +++ b/front_end/emulation/sensors.css @@ -17,7 +17,7 @@ width: 80px; } -.sensors-view input[type=text]:enabled:focus, +.sensors-view input[type=text]:not(.error-input):enabled:focus, .sensors-view select:enabled:focus { border-color: rgb(77, 144, 254); outline: none; @@ -149,4 +149,12 @@ fieldset.device-orientation-override-section[disabled] .accelerometer-stage { fieldset.device-orientation-override-section { display: flex; -} \ No newline at end of file +} + +.touch-label { + margin-top: 10px; +} + +.touch-label select { + margin-left: 10px; +} diff --git a/front_end/externs.js b/front_end/externs.js index 773a9fde6e..7906eca063 100644 --- a/front_end/externs.js +++ b/front_end/externs.js @@ -42,6 +42,9 @@ Event.prototype.isMetaOrCtrlForTest; /** @type {string} */ Event.prototype.code; +/** @type {function():!Array|undefined} */ +Event.prototype.deepPath; + /** * @type {number} */ diff --git a/front_end/main/Main.js b/front_end/main/Main.js index 02be248b87..e43bf11f8a 100644 --- a/front_end/main/Main.js +++ b/front_end/main/Main.js @@ -106,10 +106,10 @@ WebInspector.Main.prototype = { Runtime.experiments.register("applyCustomStylesheet", "Allow custom UI themes"); Runtime.experiments.register("appBanner", "App banner support", true); Runtime.experiments.register("blackboxJSFramesOnTimeline", "Blackbox JavaScript frames on Timeline", true); + Runtime.experiments.register("timelineCollapsible", "Collapsbile row groups in Timeline"); Runtime.experiments.register("colorContrastRatio", "Contrast ratio line in color picker", true); Runtime.experiments.register("cpuThrottling", "CPU throttling", true); Runtime.experiments.register("emptySourceMapAutoStepping", "Empty sourcemap auto-stepping"); - Runtime.experiments.register("fileSystemInspection", "FileSystem inspection"); Runtime.experiments.register("gpuTimeline", "GPU data on timeline", true); Runtime.experiments.register("inputEventsOnTimelineOverview", "Input events on Timeline overview", true); Runtime.experiments.register("layersPanel", "Layers panel"); @@ -215,7 +215,7 @@ WebInspector.Main.prototype = { new WebInspector.Main.InspectedNodeRevealer(); new WebInspector.NetworkPanelIndicator(); new WebInspector.SourcesPanelIndicator(); - new WebInspector.AutoAttachToCreatedPagesSync(); + new WebInspector.BackendSettingsSync(); WebInspector.domBreakpointsSidebarPane = new WebInspector.DOMBreakpointsSidebarPane(); WebInspector.actionRegistry = new WebInspector.ActionRegistry(); @@ -1082,19 +1082,22 @@ WebInspector.TargetCrashedScreen.prototype = { * @constructor * @implements {WebInspector.TargetManager.Observer} */ -WebInspector.AutoAttachToCreatedPagesSync = function() +WebInspector.BackendSettingsSync = function() { - this._setting = WebInspector.settings.moduleSetting("autoAttachToCreatedPages"); - this._setting.addChangeListener(this._update, this); + this._autoAttachSetting = WebInspector.settings.moduleSetting("autoAttachToCreatedPages"); + this._autoAttachSetting.addChangeListener(this._update, this); + this._disableJavascriptSetting = WebInspector.settings.moduleSetting("javaScriptDisabled"); + this._disableJavascriptSetting.addChangeListener(this._update, this); WebInspector.targetManager.observeTargets(this, WebInspector.Target.Type.Page); } -WebInspector.AutoAttachToCreatedPagesSync.prototype = { +WebInspector.BackendSettingsSync.prototype = { _update: function() { - var value = this._setting.get(); - for (var target of WebInspector.targetManager.targets(WebInspector.Target.Type.Page)) - target.pageAgent().setAutoAttachToCreatedPages(value); + for (var target of WebInspector.targetManager.targets(WebInspector.Target.Type.Page)) { + target.pageAgent().setAutoAttachToCreatedPages(this._autoAttachSetting.get()); + target.emulationAgent().setScriptExecutionDisabled(this._disableJavascriptSetting.get()); + } }, /** @@ -1103,7 +1106,8 @@ WebInspector.AutoAttachToCreatedPagesSync.prototype = { */ targetAdded: function(target) { - target.pageAgent().setAutoAttachToCreatedPages(this._setting.get()); + target.pageAgent().setAutoAttachToCreatedPages(this._autoAttachSetting.get()); + target.emulationAgent().setScriptExecutionDisabled(this._disableJavascriptSetting.get()); }, /** @@ -1115,4 +1119,24 @@ WebInspector.AutoAttachToCreatedPagesSync.prototype = { } } +/** + * @constructor + * @implements {WebInspector.SettingUI} + */ +WebInspector.ShowMetricsRulersSettingUI = function() +{ +} + +WebInspector.ShowMetricsRulersSettingUI.prototype = { + /** + * @override + * @return {?Element} + */ + settingElement: function() + { + return WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show rulers"), WebInspector.moduleSetting("showMetricsRulers")); + } +} + + new WebInspector.Main(); diff --git a/front_end/main/RenderingOptions.js b/front_end/main/RenderingOptions.js index 571d51a45e..60ac91c47a 100644 --- a/front_end/main/RenderingOptions.js +++ b/front_end/main/RenderingOptions.js @@ -46,6 +46,7 @@ WebInspector.RenderingOptionsView = function() this._appendCheckbox(WebInspector.UIString("Show FPS meter"), "setShowFPSCounter"); var scrollingTitle = WebInspector.UIString("Shows areas of the page that slow down scrolling:\nTouch and mousewheel event listeners can delay scrolling.\nSome areas need to repaint their content when scrolled."); this._appendCheckbox(WebInspector.UIString("Show scrolling perf issues"), "setShowScrollBottleneckRects", scrollingTitle); + this._appendCheckbox(WebInspector.UIString("Show page size on resize"), "setShowViewportSizeOnResize"); // CSS media. var mediaRow = this.contentElement.createChild("div", "media-row"); diff --git a/front_end/main/Tests.js b/front_end/main/Tests.js index e63ad97079..4e3588faf3 100644 --- a/front_end/main/Tests.js +++ b/front_end/main/Tests.js @@ -47,6 +47,7 @@ function createTestSuite(domAutomationController) function TestSuite() { WebInspector.TestBase.call(this, domAutomationController); + this._asyncInvocationId = 0; }; TestSuite.prototype = { @@ -651,10 +652,9 @@ TestSuite.prototype.waitForTestResultsInConsole = function() this.takeControl(); }; -TestSuite.prototype.invokeAsyncWithTimeline_ = function(functionName, callback) +TestSuite.prototype.startTimeline = function(callback) { - var test = this; - test.showPanel("timeline").then(function() { + this.showPanel("timeline").then(function() { WebInspector.panels.timeline._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, onRecordingStarted); WebInspector.panels.timeline._toggleRecording(); }); @@ -662,32 +662,77 @@ TestSuite.prototype.invokeAsyncWithTimeline_ = function(functionName, callback) function onRecordingStarted() { WebInspector.panels.timeline._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStarted, onRecordingStarted); - test.evaluateInConsole_(functionName + "(function() { console.log('DONE'); });", function() {}); - WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, onConsoleMessage); + callback(); + } +} + +TestSuite.prototype.stopTimeline = function(callback) +{ + WebInspector.panels.timeline._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStopped, onRecordingStopped); + WebInspector.panels.timeline._toggleRecording(); + function onRecordingStopped() + { + WebInspector.panels.timeline._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStopped, onRecordingStopped); + callback(); } +} + +TestSuite.prototype.invokePageFunctionAsync = function(functionName, opt_args, callback_is_always_last) +{ + var callback = arguments[arguments.length - 1]; + var doneMessage = `DONE: ${functionName}.${++this._asyncInvocationId}`; + var argsString = arguments.length < 3 ? "" : Array.prototype.slice.call(arguments, 1, -1).map(arg => JSON.stringify(arg)).join(",") + ","; + this.evaluateInConsole_(`${functionName}(${argsString} function() { console.log('${doneMessage}'); });`, function() {}); + WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, onConsoleMessage); function onConsoleMessage(event) { var text = event.data.messageText; - if (text === "DONE") { + if (text === doneMessage) { WebInspector.multitargetConsoleModel.removeEventListener(WebInspector.ConsoleModel.Events.MessageAdded, onConsoleMessage); - pageActionsDone(); + callback(); } } +} - function pageActionsDone() +TestSuite.prototype.invokeAsyncWithTimeline_ = function(functionName, callback) +{ + var test = this; + + this.startTimeline(onRecordingStarted); + + function onRecordingStarted() { - WebInspector.panels.timeline._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStopped, onRecordingStopped); - WebInspector.panels.timeline._toggleRecording(); + test.invokePageFunctionAsync(functionName, pageActionsDone); } - function onRecordingStopped() + function pageActionsDone() { - WebInspector.panels.timeline._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStopped, onRecordingStopped); - callback(); + test.stopTimeline(callback); } }; +TestSuite.prototype.enableExperiment = function(name) +{ + Runtime.experiments.enableForTest(name); +} + +TestSuite.prototype.checkInputEventsPresent = function() +{ + var expectedEvents = new Set(arguments); + var model = WebInspector.panels.timeline._model; + var asyncEvents = model.mainThreadAsyncEvents(); + var input = asyncEvents.get(WebInspector.TimelineUIUtils.asyncEventGroups().input) || []; + var prefix = "InputLatency::"; + for (var e of input) { + if (!e.name.startsWith(prefix)) + continue; + expectedEvents.delete(e.name.substr(prefix.length)); + } + if (expectedEvents.size) + throw "Some expected events are not found: " + Array.from(expectedEvents.keys()).join(","); +} + /** * Serializes array of uiSourceCodes to string. * @param {!Array.} uiSourceCodes diff --git a/front_end/main/module.json b/front_end/main/module.json index 841a915430..eedc6d6fde 100644 --- a/front_end/main/module.json +++ b/front_end/main/module.json @@ -287,6 +287,12 @@ "category": "Extensions", "className": "WebInspector.HandlerRegistry.OpenAnchorLocationSettingUI" }, + { + "type": "@WebInspector.SettingUI", + "category": "Elements", + "order": 3, + "className": "WebInspector.ShowMetricsRulersSettingUI" + }, { "type": "context-menu-item", "location": "mainMenu/navigate", diff --git a/front_end/network/NetworkConfigView.js b/front_end/network/NetworkConfigView.js index 7042f1bf10..e44fa6d75a 100644 --- a/front_end/network/NetworkConfigView.js +++ b/front_end/network/NetworkConfigView.js @@ -41,7 +41,7 @@ WebInspector.NetworkConfigView.prototype = { _createNetworkThrottlingSection: function() { var section = this._createSection(WebInspector.UIString("Network throttling"), "network-config-throttling"); - new WebInspector.NetworkConditionsSelector(/** @type {!HTMLSelectElement} */(section.createChild("select", "chrome-select"))); + WebInspector.NetworkConditionsSelector.decorateSelect(/** @type {!HTMLSelectElement} */(section.createChild("select", "chrome-select"))); }, _createUserAgentSection: function() diff --git a/front_end/network/NetworkPanel.js b/front_end/network/NetworkPanel.js index 34e66c14ef..79e97141c2 100644 --- a/front_end/network/NetworkPanel.js +++ b/front_end/network/NetworkPanel.js @@ -177,7 +177,7 @@ WebInspector.NetworkPanel.prototype = { { var toolbarItem = new WebInspector.ToolbarComboBox(null); toolbarItem.setMaxWidth(140); - new WebInspector.NetworkConditionsSelector(toolbarItem.selectElement()); + WebInspector.NetworkConditionsSelector.decorateSelect(toolbarItem.selectElement()); return toolbarItem; }, diff --git a/front_end/network/module.json b/front_end/network/module.json index cb64819414..f7db8b4e63 100644 --- a/front_end/network/module.json +++ b/front_end/network/module.json @@ -86,22 +86,6 @@ "persistence": "closeable", "order": 40, "className": "WebInspector.NetworkConfigView" - }, - { - "type": "setting", - "settingName": "networkConditionsCustomProfiles", - "settingType": "array", - "defaultValue": [] - }, - { - "type": "settings-view", - "name": "network-conditions", - "title": "Throttling", - "order": "35", - "className": "WebInspector.NetworkConditionsSettingsTab", - "settings": [ - "networkConditionsCustomProfiles" - ] } ], "dependencies": [ @@ -117,7 +101,6 @@ "HARWriter.js", "JSONView.js", "RequestView.js", - "NetworkConditionsSelector.js", "NetworkConfigView.js", "NetworkDataGridNode.js", "NetworkItemView.js", @@ -137,7 +120,6 @@ "resources": [ "blockedURLsPane.css", "eventSourceMessagesView.css", - "networkConditionsSettingsTab.css", "networkConfigView.css", "networkLogView.css", "networkPanel.css", diff --git a/front_end/platform/utilities.js b/front_end/platform/utilities.js index d46addb893..eb66a1445c 100644 --- a/front_end/platform/utilities.js +++ b/front_end/platform/utilities.js @@ -91,6 +91,16 @@ String.prototype.findAll = function(string) return matches; } +/** + * @return {string} + */ +String.prototype.replaceControlCharacters = function() +{ + // Replace C0 and C1 control character sets with printable character. + // Do not replace '\t', \n' and '\r'. + return this.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u0080-\u009f]/g, "�"); +} + /** * @return {boolean} */ @@ -1272,6 +1282,16 @@ Set.prototype.valuesArray = function() return Array.from(this.values()); } +/** + * @param {!Iterable} iterable + * @template T + */ +Set.prototype.addAll = function(iterable) +{ + for (var e of iterable) + this.add(e); +} + /** * @return {T} * @template T @@ -1540,3 +1560,112 @@ Promise.prototype.catchException = function(defaultValue) { return defaultValue; }); } + +/** + * @constructor + * @param {(function(!Segment, !Segment): ?Segment)=} mergeCallback + */ +function SegmentedRange(mergeCallback) +{ + /** @type {!Array} */ + this._segments = []; + this._mergeCallback = mergeCallback; +} + +/** + * @constructor + * @param {number} begin + * @param {number} end + * @param {*} data + */ +function Segment(begin, end, data) +{ + if (begin > end) + console.assert(false, "Invalid segment"); + this.begin = begin; + this.end = end; + this.data = data; +} + +Segment.prototype = { + /** + * @param {!Segment} that + * @return {boolean} + */ + intersects: function(that) + { + return this.begin < that.end && that.begin < this.end; + } +}; + +SegmentedRange.prototype = { + /** + * @param {!Segment} newSegment + */ + append: function(newSegment) + { + // 1. Find the proper insertion point for new segment + var startIndex = this._segments.lowerBound(newSegment, (a, b) => a.begin - b.begin); + var endIndex = startIndex; + var merged = null; + if (startIndex > 0) { + // 2. Try mering the preceding segment + var precedingSegment = this._segments[startIndex - 1]; + merged = this._tryMerge(precedingSegment, newSegment); + if (merged) { + --startIndex; + newSegment = merged; + } else if (this._segments[startIndex - 1].end >= newSegment.begin) { + // 2a. If merge failed and segments overlap, adjust preceding segment. + // If an old segment entirely contains new one, split it in two. + if (newSegment.end < precedingSegment.end) + this._segments.splice(startIndex, 0, new Segment(newSegment.end, precedingSegment.end, precedingSegment.data)); + precedingSegment.end = newSegment.begin; + } + } + // 3. Consume all segments that are entirely covered by the new one. + while (endIndex < this._segments.length && this._segments[endIndex].end <= newSegment.end) + ++endIndex; + // 4. Merge or adjust the succeeding segment if it overlaps. + if (endIndex < this._segments.length) { + merged = this._tryMerge(newSegment, this._segments[endIndex]); + if (merged) { + endIndex++; + newSegment = merged; + } else if (newSegment.intersects(this._segments[endIndex])) + this._segments[endIndex].begin = newSegment.end; + } + this._segments.splice(startIndex, endIndex - startIndex, newSegment); + }, + + /** + * @param {!SegmentedRange} that + */ + appendRange: function(that) + { + that.segments().forEach(segment => this.append(segment)); + }, + + /** + * @return {!Array} + */ + segments: function() + { + return this._segments; + }, + + /** + * @param {!Segment} first + * @param {!Segment} second + * @return {?Segment} + */ + _tryMerge: function(first, second) + { + var merged = this._mergeCallback && this._mergeCallback(first, second); + if (!merged) + return null; + merged.begin = first.begin; + merged.end = Math.max(first.end, second.end); + return merged; + } +} diff --git a/front_end/profiler/CPUProfileFlameChart.js b/front_end/profiler/CPUProfileFlameChart.js index 47a0dfc202..a8b20acb43 100644 --- a/front_end/profiler/CPUProfileFlameChart.js +++ b/front_end/profiler/CPUProfileFlameChart.js @@ -71,6 +71,15 @@ WebInspector.CPUFlameChartDataProvider.prototype = { return 2; }, + /** + * @return {number} + * @override + */ + groupSeparatorHeight: function() + { + return 5; + }, + /** * @override * @param {number} startTime @@ -180,7 +189,7 @@ WebInspector.CPUFlameChartDataProvider.prototype = { this._maxStackDepth = maxDepth; - this._timelineData = new WebInspector.FlameChart.TimelineData(entryLevels, entryTotalTimes, entryStartTimes); + this._timelineData = new WebInspector.FlameChart.TimelineData(entryLevels, entryTotalTimes, entryStartTimes, null); /** @type {!Array.} */ this._entryNodes = entryNodes; @@ -385,7 +394,7 @@ WebInspector.CPUProfileFlameChart = function(dataProvider) this._overviewPane = new WebInspector.CPUProfileFlameChart.OverviewPane(dataProvider); this._overviewPane.show(this.element); - this._mainPane = new WebInspector.FlameChart(dataProvider, this._overviewPane, true); + this._mainPane = new WebInspector.FlameChart(dataProvider, this._overviewPane); this._mainPane.show(this.element); this._mainPane.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this); this._overviewPane.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); diff --git a/front_end/resources/DirectoryContentView.js b/front_end/resources/DirectoryContentView.js deleted file mode 100644 index d1ba80bcf7..0000000000 --- a/front_end/resources/DirectoryContentView.js +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @constructor - * @extends {WebInspector.SortableDataGrid} - */ -WebInspector.DirectoryContentView = function() -{ - const indexes = WebInspector.DirectoryContentView.columnIndexes; - var columns = [ - {id: indexes.Name, title: WebInspector.UIString("Name"), sortable: true, sort: WebInspector.DataGrid.Order.Ascending, width: "20%"}, - {id: indexes.URL, title: WebInspector.UIString("URL"), sortable: true, width: "20%"}, - {id: indexes.Type, title: WebInspector.UIString("Type"), sortable: true, width: "15%"}, - {id: indexes.Size, title: WebInspector.UIString("Size"), sortable: true, width: "10%"}, - {id: indexes.ModificationTime, title: WebInspector.UIString("Modification Time"), sortable: true, width: "25%"} - ]; - - WebInspector.SortableDataGrid.call(this, columns); - this.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sort, this); -} - -WebInspector.DirectoryContentView.columnIndexes = { - Name: "0", - URL: "1", - Type: "2", - Size: "3", - ModificationTime: "4" -} - -WebInspector.DirectoryContentView.prototype = { - /** - * @param {!Array.} entries - */ - showEntries: function(entries) - { - const indexes = WebInspector.DirectoryContentView.columnIndexes; - this.rootNode().removeChildren(); - for (var i = 0; i < entries.length; ++i) - this.rootNode().appendChild(new WebInspector.DirectoryContentView.Node(entries[i])); - }, - - _sort: function() - { - var column = this.sortColumnIdentifier(); - if (!column) - return; - this.sortNodes(WebInspector.DirectoryContentView.Node.comparator(column), !this.isSortOrderAscending()); - }, - - __proto__: WebInspector.SortableDataGrid.prototype -} - -/** - * @constructor - * @extends {WebInspector.SortableDataGridNode} - * @param {!WebInspector.FileSystemModel.Entry} entry - */ -WebInspector.DirectoryContentView.Node = function(entry) -{ - const indexes = WebInspector.DirectoryContentView.columnIndexes; - var data = {}; - data[indexes.Name] = entry.name; - data[indexes.URL] = entry.url; - data[indexes.Type] = entry.isDirectory ? WebInspector.UIString("Directory") : entry.mimeType; - data[indexes.Size] = ""; - data[indexes.ModificationTime] = ""; - - WebInspector.SortableDataGridNode.call(this, data); - this._entry = entry; - this._metadata = null; - - this._entry.requestMetadata(this._metadataReceived.bind(this)); -} - -/** - * @param {string} column - * @return {function(!WebInspector.DataGridNode, !WebInspector.DataGridNode):number} - */ -WebInspector.DirectoryContentView.Node.comparator = function(column) -{ - const indexes = WebInspector.DirectoryContentView.columnIndexes; - - switch (column) { - case indexes.Name: - case indexes.URL: - return function(x, y) - { - return isDirectoryCompare(x, y) || nameCompare(x, y); - }; - case indexes.Type: - return function(x, y) - { - return isDirectoryCompare(x ,y) || typeCompare(x, y) || nameCompare(x, y); - }; - case indexes.Size: - return function(x, y) - { - return isDirectoryCompare(x, y) || sizeCompare(x, y) || nameCompare(x, y); - }; - case indexes.ModificationTime: - return function(x, y) - { - return isDirectoryCompare(x, y) || modificationTimeCompare(x, y) || nameCompare(x, y); - }; - default: - return WebInspector.SortableDataGrid.TrivialComparator; - } - - function isDirectoryCompare(x, y) - { - if (x._entry.isDirectory != y._entry.isDirectory) - return y._entry.isDirectory ? 1 : -1; - return 0; - } - - function nameCompare(x, y) - { - return x._entry.name.compareTo(y._entry.name); - } - - function typeCompare(x, y) - { - return (x._entry.mimeType || "").compareTo(y._entry.mimeType || ""); - } - - function sizeCompare(x, y) - { - return ((x._metadata ? x._metadata.size : 0) - (y._metadata ? y._metadata.size : 0)); - } - - function modificationTimeCompare(x, y) - { - return ((x._metadata ? x._metadata.modificationTime : 0) - (y._metadata ? y._metadata.modificationTime : 0)); - } -} - -WebInspector.DirectoryContentView.Node.prototype = { - /** - * @param {number} errorCode - * @param {!FileSystemAgent.Metadata} metadata - */ - _metadataReceived: function(errorCode, metadata) - { - const indexes = WebInspector.DirectoryContentView.columnIndexes; - if (errorCode !== 0) - return; - - this._metadata = metadata; - var data = this.data; - if (this._entry.isDirectory) - data[indexes.Size] = WebInspector.UIString("-"); - else - data[indexes.Size] = Number.bytesToString(metadata.size); - data[indexes.ModificationTime] = new Date(metadata.modificationTime).toISOString(); - this.data = data; - }, - - __proto__: WebInspector.SortableDataGridNode.prototype -} diff --git a/front_end/resources/FileContentView.js b/front_end/resources/FileContentView.js deleted file mode 100644 index ad90386662..0000000000 --- a/front_end/resources/FileContentView.js +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @constructor - * @extends {WebInspector.VBox} - * @param {!WebInspector.FileSystemModel.File} file - */ -WebInspector.FileContentView = function(file) -{ - WebInspector.VBox.call(this); - - this._innerView = /** @type {?WebInspector.Widget} */ (null); - this._file = file; - this._content = null; -} - -WebInspector.FileContentView.prototype = { - wasShown: function() - { - if (!this._innerView) { - if (this._file.isTextFile) - this._innerView = new WebInspector.EmptyWidget(""); - else - this._innerView = new WebInspector.EmptyWidget(WebInspector.UIString("Binary File")); - this.refresh(); - } - - this._innerView.show(this.element); - }, - - /** - * @param {number} errorCode - * @param {!FileSystemAgent.Metadata} metadata - */ - _metadataReceived: function(errorCode, metadata) - { - if (errorCode || !metadata) - return; - - if (this._content) { - if (!this._content.updateMetadata(metadata)) - return; - var sourceFrame = /** @type {!WebInspector.SourceFrame} */ (this._innerView); - this._content.requestContent().then(sourceFrame.setContent.bind(sourceFrame)); - } else { - this._innerView.detach(); - this._content = new WebInspector.FileContentView.FileContentProvider(this._file, metadata); - var sourceFrame = new WebInspector.SourceFrame(this._content); - sourceFrame.setHighlighterType(this._file.resourceType.canonicalMimeType()); - this._innerView = sourceFrame; - this._innerView.show(this.element); - } - }, - - refresh: function() - { - if (!this._innerView) - return; - - if (this._file.isTextFile) - this._file.requestMetadata(this._metadataReceived.bind(this)); - }, - - __proto__: WebInspector.VBox.prototype -} - -/** - * @constructor - * @implements {WebInspector.ContentProvider} - * @param {!WebInspector.FileSystemModel.File} file - * @param {!FileSystemAgent.Metadata} metadata - */ -WebInspector.FileContentView.FileContentProvider = function(file, metadata) -{ - this._file = file; - this._metadata = metadata; -} - -WebInspector.FileContentView.FileContentProvider.prototype = { - /** - * @override - * @return {string} - */ - contentURL: function() - { - return this._file.url; - }, - - /** - * @override - * @return {!WebInspector.ResourceType} - */ - contentType: function() - { - return this._file.resourceType; - }, - - /** - * @override - * @return {!Promise} - */ - requestContent: function() - { - var callback; - var promise = new Promise(fulfill => callback = fulfill); - var size = /** @type {number} */ (this._metadata.size); - this._file.requestFileContent(true, 0, size, this._charset || "", this._fileContentReceived.bind(this, callback)); - return promise; - }, - - /** - * @param {function(?string)} callback - * @param {number} errorCode - * @param {string=} content - * @param {boolean=} base64Encoded - * @param {string=} charset - */ - _fileContentReceived: function(callback, errorCode, content, base64Encoded, charset) - { - if (errorCode || !content) { - callback(null); - return; - } - - this._charset = charset; - callback(content); - }, - - /** - * @override - * @param {string} query - * @param {boolean} caseSensitive - * @param {boolean} isRegex - * @param {function(!Array.)} callback - */ - searchInContent: function(query, caseSensitive, isRegex, callback) - { - setTimeout(callback.bind(null, []), 0); - }, - - /** - * @param {!FileSystemAgent.Metadata} metadata - * @return {boolean} - */ - updateMetadata: function(metadata) - { - if (this._metadata.modificationTime >= metadata.modificationTime) - return false; - this._metadata = metadata.modificationTime; - return true; - } -} diff --git a/front_end/resources/FileSystemModel.js b/front_end/resources/FileSystemModel.js deleted file mode 100644 index 5ded5ad0e7..0000000000 --- a/front_end/resources/FileSystemModel.js +++ /dev/null @@ -1,553 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @constructor - * @extends {WebInspector.SDKObject} - * @param {!WebInspector.Target} target - */ -WebInspector.FileSystemModel = function(target) -{ - WebInspector.SDKObject.call(this, target); - - this._fileSystemsForOrigin = {}; - - target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginAdded, this._securityOriginAdded, this); - target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginRemoved, this._securityOriginRemoved, this); - this._agent = target.fileSystemAgent(); - this._agent.enable(); - - this._reset(); -} - -WebInspector.FileSystemModel.prototype = { - _reset: function() - { - for (var securityOrigin in this._fileSystemsForOrigin) - this._removeOrigin(securityOrigin); - var securityOrigins = this.target().resourceTreeModel.securityOrigins(); - for (var i = 0; i < securityOrigins.length; ++i) - this._addOrigin(securityOrigins[i]); - }, - - /** - * @param {!WebInspector.Event} event - */ - _securityOriginAdded: function(event) - { - var securityOrigin = /** @type {string} */ (event.data); - this._addOrigin(securityOrigin); - }, - - /** - * @param {!WebInspector.Event} event - */ - _securityOriginRemoved: function(event) - { - var securityOrigin = /** @type {string} */ (event.data); - this._removeOrigin(securityOrigin); - }, - - /** - * @param {string} securityOrigin - */ - _addOrigin: function(securityOrigin) - { - this._fileSystemsForOrigin[securityOrigin] = {}; - - var types = ["persistent", "temporary"]; - for (var i = 0; i < types.length; ++i) - this._requestFileSystemRoot(securityOrigin, types[i], this._fileSystemRootReceived.bind(this, securityOrigin, types[i], this._fileSystemsForOrigin[securityOrigin])); - }, - - /** - * @param {string} securityOrigin - */ - _removeOrigin: function(securityOrigin) - { - for (var type in this._fileSystemsForOrigin[securityOrigin]) { - var fileSystem = this._fileSystemsForOrigin[securityOrigin][type]; - delete this._fileSystemsForOrigin[securityOrigin][type]; - this._fileSystemRemoved(fileSystem); - } - delete this._fileSystemsForOrigin[securityOrigin]; - }, - - /** - * @param {string} origin - * @param {string} type - * @param {function(number, !FileSystemAgent.Entry=)} callback - */ - _requestFileSystemRoot: function(origin, type, callback) - { - /** - * @param {?Protocol.Error} error - * @param {number} errorCode - * @param {!FileSystemAgent.Entry=} backendRootEntry - */ - function innerCallback(error, errorCode, backendRootEntry) - { - if (error) { - callback(FileError.SECURITY_ERR); - return; - } - - callback(errorCode, backendRootEntry); - } - - this._agent.requestFileSystemRoot(origin, type, innerCallback); - }, - - /** - * @param {!WebInspector.FileSystemModel.FileSystem} fileSystem - */ - _fileSystemAdded: function(fileSystem) - { - this.dispatchEventToListeners(WebInspector.FileSystemModel.EventTypes.FileSystemAdded, fileSystem); - }, - - /** - * @param {!WebInspector.FileSystemModel.FileSystem} fileSystem - */ - _fileSystemRemoved: function(fileSystem) - { - this.dispatchEventToListeners(WebInspector.FileSystemModel.EventTypes.FileSystemRemoved, fileSystem); - }, - - refreshFileSystemList: function() - { - this._reset(); - }, - - /** - * @param {string} origin - * @param {string} type - * @param {!Object.} store - * @param {number} errorCode - * @param {!FileSystemAgent.Entry=} backendRootEntry - */ - _fileSystemRootReceived: function(origin, type, store, errorCode, backendRootEntry) - { - if (!errorCode && backendRootEntry && this._fileSystemsForOrigin[origin] === store) { - var fileSystem = new WebInspector.FileSystemModel.FileSystem(this, origin, type, backendRootEntry); - store[type] = fileSystem; - this._fileSystemAdded(fileSystem); - } - }, - - /** - * @param {!WebInspector.FileSystemModel.Directory} directory - * @param {function(number, !Array.=)} callback - */ - requestDirectoryContent: function(directory, callback) - { - this._requestDirectoryContent(directory.url, this._directoryContentReceived.bind(this, directory, callback)); - }, - - /** - * @param {string} url - * @param {function(number, !Array.=)} callback - */ - _requestDirectoryContent: function(url, callback) - { - /** - * @param {?Protocol.Error} error - * @param {number} errorCode - * @param {!Array.=} backendEntries - */ - function innerCallback(error, errorCode, backendEntries) - { - if (error) { - callback(FileError.SECURITY_ERR); - return; - } - - if (errorCode !== 0) { - callback(errorCode); - return; - } - - callback(errorCode, backendEntries); - } - - this._agent.requestDirectoryContent(url, innerCallback); - }, - - /** - * @param {!WebInspector.FileSystemModel.Directory} parentDirectory - * @param {function(number, !Array.=)} callback - * @param {number} errorCode - * @param {!Array.=} backendEntries - */ - _directoryContentReceived: function(parentDirectory, callback, errorCode, backendEntries) - { - if (!backendEntries) { - callback(errorCode); - return; - } - - var entries = []; - for (var i = 0; i < backendEntries.length; ++i) { - if (backendEntries[i].isDirectory) - entries.push(new WebInspector.FileSystemModel.Directory(this, parentDirectory.fileSystem, backendEntries[i])); - else - entries.push(new WebInspector.FileSystemModel.File(this, parentDirectory.fileSystem, backendEntries[i])); - } - - callback(errorCode, entries); - }, - - /** - * @param {!WebInspector.FileSystemModel.Entry} entry - * @param {function(number, !FileSystemAgent.Metadata=)} callback - */ - requestMetadata: function(entry, callback) - { - /** - * @param {?Protocol.Error} error - * @param {number} errorCode - * @param {!FileSystemAgent.Metadata=} metadata - */ - function innerCallback(error, errorCode, metadata) - { - if (error) { - callback(FileError.SECURITY_ERR); - return; - } - - callback(errorCode, metadata); - } - - this._agent.requestMetadata(entry.url, innerCallback); - }, - - /** - * @param {!WebInspector.FileSystemModel.File} file - * @param {boolean} readAsText - * @param {number=} start - * @param {number=} end - * @param {string=} charset - * @param {function(number, string=, string=)=} callback - */ - requestFileContent: function(file, readAsText, start, end, charset, callback) - { - this._requestFileContent(file.url, readAsText, start, end, charset, callback); - }, - - /** - * @param {string} url - * @param {boolean} readAsText - * @param {number=} start - * @param {number=} end - * @param {string=} charset - * @param {function(number, string=, string=)=} callback - */ - _requestFileContent: function(url, readAsText, start, end, charset, callback) - { - /** - * @param {?Protocol.Error} error - * @param {number} errorCode - * @param {string=} content - * @param {string=} charset - */ - function innerCallback(error, errorCode, content, charset) - { - if (error) { - if (callback) - callback(FileError.SECURITY_ERR); - return; - } - - if (callback) - callback(errorCode, content, charset); - } - - this._agent.requestFileContent(url, readAsText, start, end, charset, innerCallback); - }, - /** - * @param {!WebInspector.FileSystemModel.Entry} entry - * @param {function(number)=} callback - */ - deleteEntry: function(entry, callback) - { - var fileSystemModel = this; - if (entry === entry.fileSystem.root) - this._deleteEntry(entry.url, hookFileSystemDeletion); - else - this._deleteEntry(entry.url, callback); - - function hookFileSystemDeletion(errorCode) - { - callback(errorCode); - if (!errorCode) - fileSystemModel._removeFileSystem(entry.fileSystem); - } - }, - - /** - * @param {string} url - * @param {function(number)=} callback - */ - _deleteEntry: function(url, callback) - { - /** - * @param {?Protocol.Error} error - * @param {number} errorCode - */ - function innerCallback(error, errorCode) - { - if (error) { - if (callback) - callback(FileError.SECURITY_ERR); - return; - } - - if (callback) - callback(errorCode); - } - - this._agent.deleteEntry(url, innerCallback); - }, - - /** - * @param {!WebInspector.FileSystemModel.FileSystem} fileSystem - */ - _removeFileSystem: function(fileSystem) - { - var origin = fileSystem.origin; - var type = fileSystem.type; - if (this._fileSystemsForOrigin[origin] && this._fileSystemsForOrigin[origin][type]) { - delete this._fileSystemsForOrigin[origin][type]; - this._fileSystemRemoved(fileSystem); - - if (Object.isEmpty(this._fileSystemsForOrigin[origin])) - delete this._fileSystemsForOrigin[origin]; - } - }, - - __proto__: WebInspector.SDKObject.prototype -} - - -WebInspector.FileSystemModel.EventTypes = { - FileSystemAdded: "FileSystemAdded", - FileSystemRemoved: "FileSystemRemoved" -} - -/** - * @constructor - * @param {!WebInspector.FileSystemModel} fileSystemModel - * @param {string} origin - * @param {string} type - * @param {!FileSystemAgent.Entry} backendRootEntry - */ -WebInspector.FileSystemModel.FileSystem = function(fileSystemModel, origin, type, backendRootEntry) -{ - this.origin = origin; - this.type = type; - - this.root = new WebInspector.FileSystemModel.Directory(fileSystemModel, this, backendRootEntry); -} - -WebInspector.FileSystemModel.FileSystem.prototype = { - /** - * @type {string} - */ - get name() - { - return "filesystem:" + this.origin + "/" + this.type; - } -} - -/** - * @constructor - * @param {!WebInspector.FileSystemModel} fileSystemModel - * @param {!WebInspector.FileSystemModel.FileSystem} fileSystem - * @param {!FileSystemAgent.Entry} backendEntry - */ -WebInspector.FileSystemModel.Entry = function(fileSystemModel, fileSystem, backendEntry) -{ - this._fileSystemModel = fileSystemModel; - this._fileSystem = fileSystem; - - this._url = backendEntry.url; - this._name = backendEntry.name; - this._isDirectory = backendEntry.isDirectory; -} - -/** - * @param {!WebInspector.FileSystemModel.Entry} x - * @param {!WebInspector.FileSystemModel.Entry} y - * @return {number} - */ -WebInspector.FileSystemModel.Entry.compare = function(x, y) -{ - if (x.isDirectory != y.isDirectory) - return y.isDirectory ? 1 : -1; - return x.name.compareTo(y.name); -} - -WebInspector.FileSystemModel.Entry.prototype = { - /** - * @type {!WebInspector.FileSystemModel} - */ - get fileSystemModel() - { - return this._fileSystemModel; - }, - - /** - * @type {!WebInspector.FileSystemModel.FileSystem} - */ - get fileSystem() - { - return this._fileSystem; - }, - - /** - * @type {string} - */ - get url() - { - return this._url; - }, - - /** - * @type {string} - */ - get name() - { - return this._name; - }, - - /** - * @type {boolean} - */ - get isDirectory() - { - return this._isDirectory; - }, - - /** - * @param {function(number, !FileSystemAgent.Metadata)} callback - */ - requestMetadata: function(callback) - { - this.fileSystemModel.requestMetadata(this, callback); - }, - - /** - * @param {function(number)} callback - */ - deleteEntry: function(callback) - { - this.fileSystemModel.deleteEntry(this, callback); - } -} - -/** - * @constructor - * @extends {WebInspector.FileSystemModel.Entry} - * @param {!WebInspector.FileSystemModel} fileSystemModel - * @param {!WebInspector.FileSystemModel.FileSystem} fileSystem - * @param {!FileSystemAgent.Entry} backendEntry - */ -WebInspector.FileSystemModel.Directory = function(fileSystemModel, fileSystem, backendEntry) -{ - WebInspector.FileSystemModel.Entry.call(this, fileSystemModel, fileSystem, backendEntry); -} - -WebInspector.FileSystemModel.Directory.prototype = { - /** - * @param {function(number, !Array.)} callback - */ - requestDirectoryContent: function(callback) - { - this.fileSystemModel.requestDirectoryContent(this, callback); - }, - - __proto__: WebInspector.FileSystemModel.Entry.prototype -} - -/** - * @constructor - * @extends {WebInspector.FileSystemModel.Entry} - * @param {!WebInspector.FileSystemModel} fileSystemModel - * @param {!WebInspector.FileSystemModel.FileSystem} fileSystem - * @param {!FileSystemAgent.Entry} backendEntry - */ -WebInspector.FileSystemModel.File = function(fileSystemModel, fileSystem, backendEntry) -{ - WebInspector.FileSystemModel.Entry.call(this, fileSystemModel, fileSystem, backendEntry); - - this._mimeType = backendEntry.mimeType; - this._resourceType = WebInspector.resourceTypes[backendEntry.resourceType]; - this._isTextFile = backendEntry.isTextFile; -} - -WebInspector.FileSystemModel.File.prototype = { - /** - * @type {string} - */ - get mimeType() - { - return this._mimeType; - }, - - /** - * @type {!WebInspector.ResourceType} - */ - get resourceType() - { - return this._resourceType; - }, - - /** - * @type {boolean} - */ - get isTextFile() - { - return this._isTextFile; - }, - - /** - * @param {boolean} readAsText - * @param {number=} start - * @param {number=} end - * @param {string=} charset - * @param {function(number, string=)=} callback - */ - requestFileContent: function(readAsText, start, end, charset, callback) - { - this.fileSystemModel.requestFileContent(this, readAsText, start, end, charset, callback); - }, - - __proto__: WebInspector.FileSystemModel.Entry.prototype -} diff --git a/front_end/resources/FileSystemView.js b/front_end/resources/FileSystemView.js deleted file mode 100644 index d3721765d9..0000000000 --- a/front_end/resources/FileSystemView.js +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @constructor - * @extends {WebInspector.SplitWidget} - * @param {!WebInspector.FileSystemModel.FileSystem} fileSystem - */ -WebInspector.FileSystemView = function(fileSystem) -{ - WebInspector.SplitWidget.call(this, true, false, "fileSystemViewSplitViewState"); - this.element.classList.add("file-system-view", "storage-view"); - - var vbox = new WebInspector.VBox(); - vbox.element.classList.add("sidebar"); - this._directoryTree = new TreeOutline(); - this._directoryTree.element.classList.add("outline-disclosure", "filesystem-directory-tree"); - vbox.element.appendChild(this._directoryTree.element); - this.setSidebarWidget(vbox); - - var rootItem = new WebInspector.FileSystemView.EntryTreeElement(this, fileSystem.root); - rootItem.expanded = true; - this._directoryTree.appendChild(rootItem); - this._visibleView = null; - - this._refreshButton = new WebInspector.ToolbarButton(WebInspector.UIString("Refresh"), "refresh-toolbar-item"); - this._refreshButton.setVisible(true); - this._refreshButton.addEventListener("click", this._refresh, this); - - this._deleteButton = new WebInspector.ToolbarButton(WebInspector.UIString("Delete"), "delete-toolbar-item"); - this._deleteButton.setVisible(true); - this._deleteButton.addEventListener("click", this._confirmDelete, this); -} - -WebInspector.FileSystemView.prototype = { - /** - * @return {!Array.} - */ - toolbarItems: function() - { - return [this._refreshButton, this._deleteButton]; - }, - - /** - * @type {!WebInspector.Widget} - */ - get visibleView() - { - return this._visibleView; - }, - - /** - * @param {!WebInspector.Widget} view - */ - showView: function(view) - { - if (this._visibleView === view) - return; - if (this._visibleView) - this._visibleView.detach(); - this._visibleView = view; - this.setMainWidget(view); - }, - - _refresh: function() - { - this._directoryTree.firstChild().refresh(); - }, - - _confirmDelete: function() - { - if (confirm(WebInspector.UIString("Are you sure you want to delete the selected entry?"))) - this._delete(); - }, - - _delete: function() - { - this._directoryTree.selectedTreeElement.deleteEntry(); - }, - - __proto__: WebInspector.SplitWidget.prototype -} - -/** - * @constructor - * @extends {TreeElement} - * @param {!WebInspector.FileSystemView} fileSystemView - * @param {!WebInspector.FileSystemModel.Entry} entry - */ -WebInspector.FileSystemView.EntryTreeElement = function(fileSystemView, entry) -{ - TreeElement.call(this, entry.name, entry.isDirectory); - - this._entry = entry; - this._fileSystemView = fileSystemView; -} - -WebInspector.FileSystemView.EntryTreeElement.prototype = { - /** - * @override - */ - onattach: function() - { - var selection = this.listItemElement.createChild("div", "selection fill"); - this.listItemElement.insertBefore(selection, this.listItemElement.firstChild); - }, - - /** - * @override - * @return {boolean} - */ - onselect: function() - { - if (!this._view) { - if (this._entry.isDirectory) - this._view = new WebInspector.DirectoryContentView(); - else { - var file = /** @type {!WebInspector.FileSystemModel.File} */ (this._entry); - this._view = new WebInspector.FileContentView(file); - } - } - this._fileSystemView.showView(this._view); - this.refresh(); - return false; - }, - - /** - * @override - */ - onpopulate: function() - { - this.refresh(); - }, - - /** - * @param {number} errorCode - * @param {!Array.=} entries - */ - _directoryContentReceived: function(errorCode, entries) - { - WebInspector.userMetrics.actionTaken(WebInspector.UserMetrics.Action.FileSystemDirectoryContentReceived); - if (errorCode === FileError.NOT_FOUND_ERR) { - if (this.parent) - this.parent.refresh(); - return; - } - - if (errorCode !== 0 || !entries) { - console.error("Failed to read directory: " + errorCode); - return; - } - - entries.sort(WebInspector.FileSystemModel.Entry.compare); - if (this._view) - this._view.showEntries(entries); - - var oldChildren = this.children().slice(0); - - var newEntryIndex = 0; - var oldChildIndex = 0; - var currentTreeItem = 0; - while (newEntryIndex < entries.length && oldChildIndex < oldChildren.length) { - var newEntry = entries[newEntryIndex]; - var oldChild = oldChildren[oldChildIndex]; - var order = newEntry.name.compareTo(oldChild._entry.name); - - if (order === 0) { - if (oldChild._entry.isDirectory) - oldChild.invalidateChildren(); - else - oldChild.refresh(); - - ++newEntryIndex; - ++oldChildIndex; - ++currentTreeItem; - continue; - } - if (order < 0) { - this.insertChild(new WebInspector.FileSystemView.EntryTreeElement(this._fileSystemView, newEntry), currentTreeItem); - ++newEntryIndex; - ++currentTreeItem; - continue; - } - - this.removeChildAtIndex(currentTreeItem); - ++oldChildIndex; - } - for (; newEntryIndex < entries.length; ++newEntryIndex) - this.appendChild(new WebInspector.FileSystemView.EntryTreeElement(this._fileSystemView, entries[newEntryIndex])); - - for (; oldChildIndex < oldChildren.length; ++oldChildIndex) - this.removeChild(oldChildren[oldChildIndex]); - }, - - refresh: function() - { - if (!this._entry.isDirectory) { - if (this._view && this._view === this._fileSystemView.visibleView) { - var fileContentView = /** @type {!WebInspector.FileContentView} */ (this._view); - fileContentView.refresh(); - } - } else - this._entry.requestDirectoryContent(this._directoryContentReceived.bind(this)); - }, - - deleteEntry: function() - { - this._entry.deleteEntry(this._deletionCompleted.bind(this)); - }, - - _deletionCompleted: function() - { - if (this._entry != this._entry.fileSystem.root) - this.parent.refresh(); - }, - - __proto__: TreeElement.prototype -} diff --git a/front_end/resources/IndexedDBModel.js b/front_end/resources/IndexedDBModel.js index c739ab8ba4..c3853060ce 100644 --- a/front_end/resources/IndexedDBModel.js +++ b/front_end/resources/IndexedDBModel.js @@ -330,7 +330,7 @@ WebInspector.IndexedDBModel.prototype = { if (!this._databaseNamesBySecurityOrigin[databaseId.securityOrigin]) return; - var databaseModel = new WebInspector.IndexedDBModel.Database(databaseId, databaseWithObjectStores.intVersion); + var databaseModel = new WebInspector.IndexedDBModel.Database(databaseId, databaseWithObjectStores.version); this._databases.set(databaseId, databaseModel); for (var i = 0; i < databaseWithObjectStores.objectStores.length; ++i) { var objectStore = databaseWithObjectStores.objectStores[i]; @@ -459,12 +459,12 @@ WebInspector.IndexedDBModel.DatabaseId.prototype = { /** * @constructor * @param {!WebInspector.IndexedDBModel.DatabaseId} databaseId - * @param {number} intVersion + * @param {number} version */ -WebInspector.IndexedDBModel.Database = function(databaseId, intVersion) +WebInspector.IndexedDBModel.Database = function(databaseId, version) { this.databaseId = databaseId; - this.intVersion = intVersion; + this.version = version; this.objectStores = {}; } diff --git a/front_end/resources/IndexedDBViews.js b/front_end/resources/IndexedDBViews.js index c607d8a6c6..7ae1cc86d2 100644 --- a/front_end/resources/IndexedDBViews.js +++ b/front_end/resources/IndexedDBViews.js @@ -54,13 +54,9 @@ WebInspector.IDBDatabaseView = function(database) this._nameTreeElement.selectable = false; this._headersTreeOutline.appendChild(this._nameTreeElement); - this._intVersionTreeElement = new TreeElement(); - this._intVersionTreeElement.selectable = false; - this._headersTreeOutline.appendChild(this._intVersionTreeElement); - - this._stringVersionTreeElement = new TreeElement(); - this._stringVersionTreeElement.selectable = false; - this._headersTreeOutline.appendChild(this._stringVersionTreeElement); + this._versionTreeElement = new TreeElement(); + this._versionTreeElement.selectable = false; + this._headersTreeOutline.appendChild(this._versionTreeElement); this.update(database); } @@ -91,8 +87,7 @@ WebInspector.IDBDatabaseView.prototype = { { this._securityOriginTreeElement.title = this._formatHeader(WebInspector.UIString("Security origin"), this._database.databaseId.securityOrigin); this._nameTreeElement.title = this._formatHeader(WebInspector.UIString("Name"), this._database.databaseId.name); - this._stringVersionTreeElement.title = this._formatHeader(WebInspector.UIString("String Version"), this._database.version); - this._intVersionTreeElement.title = this._formatHeader(WebInspector.UIString("Integer Version"), this._database.intVersion); + this._versionTreeElement.title = this._formatHeader(WebInspector.UIString("Version"), this._database.version); }, /** diff --git a/front_end/resources/ResourcesPanel.js b/front_end/resources/ResourcesPanel.js index a23ddbea9a..eba4a9b594 100644 --- a/front_end/resources/ResourcesPanel.js +++ b/front_end/resources/ResourcesPanel.js @@ -69,11 +69,6 @@ WebInspector.ResourcesPanel = function() this.cacheStorageListTreeElement = new WebInspector.ServiceWorkerCacheTreeElement(this); this._sidebarTree.appendChild(this.cacheStorageListTreeElement); - if (Runtime.experiments.isEnabled("fileSystemInspection")) { - this.fileSystemListTreeElement = new WebInspector.FileSystemListTreeElement(this); - this._sidebarTree.appendChild(this.fileSystemListTreeElement); - } - var mainContainer = new WebInspector.VBox(); this.storageViews = mainContainer.element.createChild("div", "vbox flex-auto"); this._storageViewToolbar = new WebInspector.Toolbar("resources-toolbar", mainContainer.element); @@ -164,8 +159,6 @@ WebInspector.ResourcesPanel.prototype = { this._populateApplicationCacheTree(); this.indexedDBListTreeElement._initialize(); this.cacheStorageListTreeElement._initialize(); - if (Runtime.experiments.isEnabled("fileSystemInspection")) - this.fileSystemListTreeElement._initialize(); this._initDefaultSelection(); this._initialized = true; }, @@ -1670,76 +1663,6 @@ WebInspector.IndexedDBTreeElement.prototype = { __proto__: WebInspector.StorageCategoryTreeElement.prototype } -/** - * @constructor - * @extends {WebInspector.StorageCategoryTreeElement} - * @param {!WebInspector.ResourcesPanel} storagePanel - */ -WebInspector.FileSystemListTreeElement = function(storagePanel) -{ - WebInspector.StorageCategoryTreeElement.call(this, storagePanel, WebInspector.UIString("FileSystem"), "FileSystem", ["file-system-storage-tree-item"]); -} - -WebInspector.FileSystemListTreeElement.prototype = { - _initialize: function() - { - this._refreshFileSystem(); - }, - - onattach: function() - { - WebInspector.StorageCategoryTreeElement.prototype.onattach.call(this); - this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); - }, - - _handleContextMenuEvent: function(event) - { - var contextMenu = new WebInspector.ContextMenu(event); - contextMenu.appendItem(WebInspector.UIString.capitalize("Refresh FileSystem ^list"), this._refreshFileSystem.bind(this)); - contextMenu.show(); - }, - - _fileSystemAdded: function(event) - { - var fileSystem = /** @type {!WebInspector.FileSystemModel.FileSystem} */ (event.data); - var fileSystemTreeElement = new WebInspector.FileSystemTreeElement(this._storagePanel, fileSystem); - this.appendChild(fileSystemTreeElement); - }, - - _fileSystemRemoved: function(event) - { - var fileSystem = /** @type {!WebInspector.FileSystemModel.FileSystem} */ (event.data); - var fileSystemTreeElement = this._fileSystemTreeElementByName(fileSystem.name); - if (!fileSystemTreeElement) - return; - fileSystemTreeElement.clear(); - this.removeChild(fileSystemTreeElement); - }, - - _fileSystemTreeElementByName: function(fileSystemName) - { - for (var child of this.children()) { - var fschild = /** @type {!WebInspector.FileSystemTreeElement} */ (child); - if (fschild.fileSystemName === fileSystemName) - return fschild; - } - return null; - }, - - _refreshFileSystem: function() - { - if (!this._fileSystemModel) { - this._fileSystemModel = new WebInspector.FileSystemModel(this.target()); - this._fileSystemModel.addEventListener(WebInspector.FileSystemModel.EventTypes.FileSystemAdded, this._fileSystemAdded, this); - this._fileSystemModel.addEventListener(WebInspector.FileSystemModel.EventTypes.FileSystemRemoved, this._fileSystemRemoved, this); - } - - this._fileSystemModel.refreshFileSystemList(); - }, - - __proto__: WebInspector.StorageCategoryTreeElement.prototype -} - /** * @constructor * @extends {WebInspector.BaseStorageTreeElement} @@ -2230,51 +2153,6 @@ WebInspector.ApplicationCacheFrameTreeElement.prototype = { __proto__: WebInspector.BaseStorageTreeElement.prototype } -/** - * @constructor - * @extends {WebInspector.BaseStorageTreeElement} - * @param {!WebInspector.ResourcesPanel} storagePanel - * @param {!WebInspector.FileSystemModel.FileSystem} fileSystem - */ -WebInspector.FileSystemTreeElement = function(storagePanel, fileSystem) -{ - var displayName = fileSystem.type + " - " + fileSystem.origin; - WebInspector.BaseStorageTreeElement.call(this, storagePanel, displayName, ["file-system-storage-tree-item"]); - this._fileSystem = fileSystem; -} - -WebInspector.FileSystemTreeElement.prototype = { - get fileSystemName() - { - return this._fileSystem.name; - }, - - get itemURL() - { - return "filesystem://" + this._fileSystem.name; - }, - - /** - * @override - * @return {boolean} - */ - onselect: function(selectedByUser) - { - WebInspector.BaseStorageTreeElement.prototype.onselect.call(this, selectedByUser); - this._fileSystemView = new WebInspector.FileSystemView(this._fileSystem); - this._storagePanel.showFileSystem(this._fileSystemView); - return false; - }, - - clear: function() - { - if (this.fileSystemView && this._storagePanel.visibleView === this.fileSystemView) - this._storagePanel.closeVisibleView(); - }, - - __proto__: WebInspector.BaseStorageTreeElement.prototype -} - /** * @constructor * @extends {WebInspector.VBox} diff --git a/front_end/resources/module.json b/front_end/resources/module.json index 135b67e21e..29dc86bf50 100644 --- a/front_end/resources/module.json +++ b/front_end/resources/module.json @@ -22,12 +22,8 @@ "DOMStorageItemsView.js", "DatabaseQueryView.js", "DatabaseTableView.js", - "DirectoryContentView.js", "IndexedDBModel.js", "IndexedDBViews.js", - "FileContentView.js", - "FileSystemModel.js", - "FileSystemView.js", "ResourcesPanel.js", "ServiceWorkerCacheViews.js", "ServiceWorkersView.js" diff --git a/front_end/sass/ASTService.js b/front_end/sass/ASTService.js new file mode 100644 index 0000000000..4d6e2baeba --- /dev/null +++ b/front_end/sass/ASTService.js @@ -0,0 +1,45 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + */ +WebInspector.ASTService = function() +{ + this._cssParserService = new WebInspector.CSSParserService(); + this._sassInitPromise = self.runtime.instancePromise(WebInspector.TokenizerFactory); + this._terminated = false; +} + +WebInspector.ASTService.prototype = { + /** + * @param {string} url + * @param {string} text + * @return {!Promise} + */ + parseCSS: function(url, text) + { + console.assert(!this._terminated, "Illegal call parseCSS on terminated ASTService."); + return WebInspector.SASSSupport.parseCSS(this._cssParserService, url, text); + }, + + /** + * @param {string} url + * @param {string} text + * @return {!Promise} + */ + parseSCSS: function(url, text) + { + console.assert(!this._terminated, "Illegal call parseSCSS on terminated ASTService."); + return this._sassInitPromise.then(tokenizer => WebInspector.SASSSupport.parseSCSS(tokenizer, url, text)); + }, + + dispose: function() + { + if (this._terminated) + return; + this._terminated = true; + this._cssParserService.dispose(); + }, +} diff --git a/front_end/sass/ASTSourceMap.js b/front_end/sass/ASTSourceMap.js new file mode 100644 index 0000000000..989c303bd0 --- /dev/null +++ b/front_end/sass/ASTSourceMap.js @@ -0,0 +1,247 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + * @param {string} cssURL + * @param {!Map} models + */ +WebInspector.ASTSourceMap = function(cssURL, models) +{ + this._cssURL = cssURL; + /** @type {!Map} */ + this._models = models; + /** @type {!Map} */ + this._cssToSass = new Map(); + /** @type {!Multimap} */ + this._sassToCss = new Multimap(); +} + +/** + * @param {!WebInspector.ASTService} astService + * @param {!WebInspector.CSSStyleModel} cssModel + * @param {!WebInspector.SourceMap} sourceMap + * @return {!Promise} + */ +WebInspector.ASTSourceMap.fromSourceMap = function(astService, cssModel, sourceMap) +{ + var headerIds = cssModel.styleSheetIdsForURL(sourceMap.compiledURL()); + if (!headerIds || !headerIds.length) + return Promise.resolve(/** @type {?WebInspector.ASTSourceMap} */(null)); + var header = cssModel.styleSheetHeaderForId(headerIds[0]); + + /** @type {!Map} */ + var models = new Map(); + var promises = []; + for (var url of sourceMap.sources()) { + var contentProvider = sourceMap.sourceContentProvider(url, WebInspector.resourceTypes.SourceMapStyleSheet); + var sassPromise = contentProvider.requestContent() + .then(onSCSSText.bind(null, url)) + .then(ast => models.set(ast.document.url, ast)); + promises.push(sassPromise); + } + var cssURL = sourceMap.compiledURL(); + var cssPromise = header.requestContent() + .then(text => astService.parseCSS(cssURL, text || "")) + .then(ast => models.set(ast.document.url, ast)); + promises.push(cssPromise); + + return Promise.all(promises) + .then(() => onParsed(cssURL, models, sourceMap)) + .catchException(/** @type {?WebInspector.ASTSourceMap} */(null)); + + /** + * @param {string} url + * @param {?string} text + * @return {!Promise} + */ + function onSCSSText(url, text) + { + return astService.parseSCSS(url, text || ""); + } + + /** + * @param {string} cssURL + * @param {!Map} models + * @param {!WebInspector.SourceMap} sourceMap + * @return {!WebInspector.ASTSourceMap} + */ + function onParsed(cssURL, models, sourceMap) + { + var map = new WebInspector.ASTSourceMap(cssURL, models); + //FIXME: this works O(N^2). + map.cssAST().visit(onNode); + return map; + + /** + * @param {!WebInspector.SASSSupport.Node} cssNode + */ + function onNode(cssNode) + { + if (!(cssNode instanceof WebInspector.SASSSupport.TextNode)) + return; + var entry = sourceMap.findEntry(cssNode.range.endLine, cssNode.range.endColumn); + if (!entry || !entry.sourceURL || typeof entry.sourceLineNumber === "undefined" || typeof entry.sourceColumnNumber === "undefined") + return; + var sassAST = models.get(entry.sourceURL); + if (!sassAST) + return; + var sassNode = sassAST.findNodeForPosition(entry.sourceLineNumber, entry.sourceColumnNumber); + if (sassNode) + map.mapCssToSass(cssNode, sassNode); + } + } +} + +WebInspector.ASTSourceMap.prototype = { + /** + * @return {string} + */ + cssURL: function() + { + return this._cssURL; + }, + + /** + * @return {!WebInspector.SASSSupport.AST} + */ + cssAST: function() + { + return /** @type {!WebInspector.SASSSupport.AST} */(this._models.get(this._cssURL)); + }, + + /** + * @return {!Map} + */ + sassModels: function() + { + var sassModels = new Map(this._models); + sassModels.delete(this._cssURL); + return sassModels; + }, + + /** + * @return {!Map} + */ + models: function() + { + return new Map(this._models); + }, + + /** + * @param {string} url + * @return {?WebInspector.SASSSupport.AST} + */ + modelForURL: function(url) + { + return this._models.get(url) || null; + }, + + /** + * @param {!WebInspector.SASSSupport.TextNode} css + * @param {!WebInspector.SASSSupport.TextNode} sass + */ + mapCssToSass: function(css, sass) + { + this._cssToSass.set(css, sass); + this._sassToCss.set(sass, css); + }, + + /** + * @param {!WebInspector.SASSSupport.TextNode} css + * @param {!WebInspector.SASSSupport.TextNode} sass + */ + unmapCssFromSass: function(css, sass) + { + this._cssToSass.delete(css); + this._sassToCss.remove(sass, css); + }, + + /** + * @param {!WebInspector.SASSSupport.TextNode} css + * @return {?WebInspector.SASSSupport.TextNode} + */ + toSASSNode: function(css) + { + return this._cssToSass.get(css) || null; + }, + + /** + * @param {!WebInspector.SASSSupport.TextNode} sass + * @return {!Array} + */ + toCSSNodes: function(sass) + { + var cssNodes = this._sassToCss.get(sass); + return cssNodes ? cssNodes.valuesArray() : []; + }, + + /** + * @param {!WebInspector.SASSSupport.Property} cssProperty + * @return {?WebInspector.SASSSupport.Property} + */ + toSASSProperty: function(cssProperty) + { + var sassName = this._cssToSass.get(cssProperty.name); + return sassName ? sassName.parent : null; + }, + + /** + * @param {!WebInspector.SASSSupport.Property} sassProperty + * @return {!Array} + */ + toCSSProperties: function(sassProperty) + { + return this.toCSSNodes(sassProperty.name).map(name => name.parent); + }, + + /** + * @param {!Array} updated + * @param {!Map=} outNodeMapping + * @return {?WebInspector.ASTSourceMap} + */ + rebase: function(updated, outNodeMapping) + { + outNodeMapping = outNodeMapping || new Map(); + outNodeMapping.clear(); + + var models = new Map(this._models); + for (var newAST of updated) { + var oldAST = models.get(newAST.document.url); + if (!oldAST.match(newAST, outNodeMapping)) + return null; + models.set(newAST.document.url, newAST); + } + + var newMap = new WebInspector.ASTSourceMap(this._cssURL, models); + var cssNodes = this._cssToSass.keysArray(); + for (var i = 0; i < cssNodes.length; ++i) { + var cssNode = cssNodes[i]; + var sassNode = /** @type {!WebInspector.SASSSupport.TextNode} */(this._cssToSass.get(cssNode)); + var mappedCSSNode = /** @type {!WebInspector.SASSSupport.TextNode} */(outNodeMapping.get(cssNode) || cssNode); + var mappedSASSNode = /** @type {!WebInspector.SASSSupport.TextNode} */(outNodeMapping.get(sassNode) || sassNode); + newMap.mapCssToSass(mappedCSSNode, mappedSASSNode); + } + return newMap; + }, + + /** + * @return {boolean} + */ + isValid: function() + { + var cssNodes = this._cssToSass.keysArray(); + for (var i = 0; i < cssNodes.length; ++i) { + var cssNode = cssNodes[i]; + if (!cssNode.parent || !(cssNode.parent instanceof WebInspector.SASSSupport.Property)) + continue; + if (cssNode !== cssNode.parent.name) + continue; + var sassNode = this._cssToSass.get(cssNode); + if (sassNode && cssNode.text.trim() !== sassNode.text.trim()) + return false; + } + return true; + } +} diff --git a/front_end/sass/SASSLiveSourceMap.js b/front_end/sass/SASSLiveSourceMap.js deleted file mode 100644 index 6c49db3c5e..0000000000 --- a/front_end/sass/SASSLiveSourceMap.js +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -WebInspector.SASSLiveSourceMap = {} - -/** - * @param {!WebInspector.SourceMapTracker} tracker - * @param {!WebInspector.CSSParser} cssParser - * @param {!WebInspector.TokenizerFactory} tokenizer - * @return {!Promise} - */ -WebInspector.SASSLiveSourceMap._loadMapping = function(tracker, cssParser, tokenizer) -{ - var sassModels = new Map(); - var cssAST = null; - var promises = []; - for (var url of tracker.sassURLs()) { - var sassPromise = tracker.content(url) - .then(text => WebInspector.SASSSupport.parseSCSS(url, text, tokenizer)) - .then(ast => sassModels.set(url, ast)); - promises.push(sassPromise); - } - var cssPromise = tracker.content(tracker.cssURL()) - .then(text => WebInspector.SASSSupport.parseCSS(cssParser, tracker.cssURL(), text)) - .then(ast => cssAST = ast); - promises.push(cssPromise); - - return Promise.all(promises) - .then(() => WebInspector.SASSLiveSourceMap.CSSToSASSMapping.fromSourceMap(tracker.sourceMap(), cssAST, sassModels)) - .catchException(/** @type {?WebInspector.SASSLiveSourceMap.CSSToSASSMapping} */(null)); -} - -/** - * @constructor - * @param {!WebInspector.SASSSupport.AST} cssAST - * @param {!Map} sassModels - */ -WebInspector.SASSLiveSourceMap.CSSToSASSMapping = function(cssAST, sassModels) -{ - this._cssAST = cssAST; - this._sassModels = sassModels; - /** @type {!Map} */ - this._cssToSass = new Map(); - /** @type {!Multimap} */ - this._sassToCss = new Multimap(); -} - -/** - * @param {!WebInspector.SourceMap} sourceMap - * @param {!WebInspector.SASSSupport.AST} cssAST - * @param {!Map} sassModels - * @return {!WebInspector.SASSLiveSourceMap.CSSToSASSMapping} - */ -WebInspector.SASSLiveSourceMap.CSSToSASSMapping.fromSourceMap = function(sourceMap, cssAST, sassModels) -{ - var mapping = new WebInspector.SASSLiveSourceMap.CSSToSASSMapping(cssAST, sassModels); - //FIXME: this works O(N^2). - cssAST.visit(map); - return mapping; - - /** - * @param {!WebInspector.SASSSupport.Node} cssNode - */ - function map(cssNode) - { - if (!(cssNode instanceof WebInspector.SASSSupport.TextNode)) - return; - var entry = sourceMap.findEntry(cssNode.range.endLine, cssNode.range.endColumn); - if (!entry || !entry.sourceURL || typeof entry.sourceLineNumber === "undefined" || typeof entry.sourceColumnNumber === "undefined") - return; - var sassAST = sassModels.get(entry.sourceURL); - if (!sassAST) - return; - var sassNode = sassAST.findNodeForPosition(entry.sourceLineNumber, entry.sourceColumnNumber); - if (sassNode) - mapping.mapCssToSass(cssNode, sassNode); - } -} - -WebInspector.SASSLiveSourceMap.CSSToSASSMapping.prototype = { - /** - * @return {!WebInspector.SASSSupport.AST} - */ - cssAST: function() - { - return this._cssAST; - }, - - /** - * @return {!Map} - */ - sassModels: function() - { - return this._sassModels; - }, - - /** - * @param {!WebInspector.SASSSupport.TextNode} css - * @param {!WebInspector.SASSSupport.TextNode} sass - */ - mapCssToSass: function(css, sass) - { - this._cssToSass.set(css, sass); - this._sassToCss.set(sass, css); - }, - - /** - * @param {!WebInspector.SASSSupport.TextNode} css - * @param {!WebInspector.SASSSupport.TextNode} sass - */ - unmapCssFromSass: function(css, sass) - { - this._cssToSass.delete(css); - this._sassToCss.remove(sass, css); - }, - - /** - * @param {!WebInspector.SASSSupport.TextNode} css - * @return {?WebInspector.SASSSupport.TextNode} - */ - toSASSNode: function(css) - { - return this._cssToSass.get(css) || null; - }, - - /** - * @param {!WebInspector.SASSSupport.TextNode} sass - * @return {!Array} - */ - toCSSNodes: function(sass) - { - var cssNodes = this._sassToCss.get(sass); - return cssNodes ? cssNodes.valuesArray() : []; - }, - - /** - * @param {!WebInspector.SASSSupport.Property} cssProperty - * @return {?WebInspector.SASSSupport.Property} - */ - toSASSProperty: function(cssProperty) - { - var sassName = this._cssToSass.get(cssProperty.name); - return sassName ? sassName.parent : null; - }, - - /** - * @param {!WebInspector.SASSSupport.Property} sassProperty - * @return {!Array} - */ - toCSSProperties: function(sassProperty) - { - return this.toCSSNodes(sassProperty.name).map(name => name.parent); - }, - - /** - * @param {!WebInspector.SASSSupport.ASTDiff} cssDiff - * @return {!WebInspector.SASSLiveSourceMap.CSSToSASSMapping} - */ - rebaseForCSSDiff: function(cssDiff) - { - var newMapping = new WebInspector.SASSLiveSourceMap.CSSToSASSMapping(cssDiff.newAST, this._sassModels); - var cssNodes = this._cssToSass.keysArray(); - for (var i = 0; i < cssNodes.length; ++i) { - var cssNode = cssNodes[i]; - var sassNode = this._cssToSass.get(cssNode); - var mappedNode = cssDiff.mapping.get(cssNode); - if (mappedNode && sassNode) - newMapping.mapCssToSass(mappedNode, sassNode); - } - return newMapping; - }, - - /** - * @param {!WebInspector.SASSSupport.ASTDiff} sassDiff - * @return {!WebInspector.SASSLiveSourceMap.CSSToSASSMapping} - */ - rebaseForSASSDiff: function(sassDiff) - { - var sassModels = new Map(this._sassModels); - sassModels.set(sassDiff.url, sassDiff.newAST); - var newMapping = new WebInspector.SASSLiveSourceMap.CSSToSASSMapping(this._cssAST, sassModels); - var cssNodes = this._cssToSass.keysArray(); - for (var i = 0; i < cssNodes.length; ++i) { - var cssNode = cssNodes[i]; - var sassNode = this._cssToSass.get(cssNode); - var mappedNode = sassNode.document.url === sassDiff.url ? sassDiff.mapping.get(sassNode) : sassNode; - if (mappedNode) - newMapping.mapCssToSass(cssNode, mappedNode); - } - return newMapping; - }, - - /** - * @return {boolean} - */ - isValid: function() - { - var cssNodes = this._cssToSass.keysArray(); - for (var i = 0; i < cssNodes.length; ++i) { - var cssNode = cssNodes[i]; - if (!cssNode.parent || !(cssNode.parent instanceof WebInspector.SASSSupport.Property)) - continue; - if (cssNode !== cssNode.parent.name) - continue; - var sassNode = this._cssToSass.get(cssNode); - if (sassNode && cssNode.text.trim() !== sassNode.text.trim()) - return false; - } - return true; - } -} diff --git a/front_end/sass/SASSProcessor.js b/front_end/sass/SASSProcessor.js new file mode 100644 index 0000000000..ecb489b5b8 --- /dev/null +++ b/front_end/sass/SASSProcessor.js @@ -0,0 +1,548 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + * @param {!WebInspector.ASTService} astService + * @param {!WebInspector.ASTSourceMap} map + * @param {!Array} editOperations + */ +WebInspector.SASSProcessor = function(astService, map, editOperations) +{ + this._astService = astService; + this._map = map; + this._editOperations = editOperations; +} + +WebInspector.SASSProcessor.prototype = { + /** + * @return {!Promise} + */ + _mutate: function() + { + /** @type {!Set} */ + var changedCSSRules = new Set(); + for (var editOperation of this._editOperations) { + var rules = editOperation.perform(); + changedCSSRules.addAll(rules); + } + + // Reparse new texts, make sure changes result in anticipated AST trees. + var promises = []; + for (var ast of this._map.models().values()) { + if (!ast.document.hasChanged()) + continue; + var promise; + if (ast.document.url === this._map.cssURL()) + promise = this._astService.parseCSS(ast.document.url, ast.document.newText()); + else + promise = this._astService.parseSCSS(ast.document.url, ast.document.newText()); + promises.push(promise); + } + + return Promise.all(promises) + .then(this._onFinished.bind(this, changedCSSRules)); + }, + + /** + * @param {!Set} changedCSSRules + * @param {!Array} changedModels + * @return {?WebInspector.SASSProcessor.Result} + */ + _onFinished: function(changedCSSRules, changedModels) + { + var nodeMapping = new Map(); + var map = this._map.rebase(changedModels, nodeMapping); + if (!map) + return null; + + var cssEdits = []; + for (var rule of changedCSSRules) { + var oldRange = rule.styleRange; + var newRule = nodeMapping.get(rule); + var newText = newRule.styleRange.extract(newRule.document.text); + cssEdits.push(new WebInspector.SourceEdit(newRule.document.url, oldRange, newText)); + } + + /** @type {!Map} */ + var newSASSSources = new Map(); + for (var model of changedModels) { + if (model.document.url === map.cssURL()) + continue; + newSASSSources.set(model.document.url, model.document.text); + } + return new WebInspector.SASSProcessor.Result(map, cssEdits, newSASSSources); + } +} + +/** + * @constructor + * @param {!WebInspector.ASTSourceMap} map + * @param {!Array} cssEdits + * @param {!Map} newSASSSources + */ +WebInspector.SASSProcessor.Result = function(map, cssEdits, newSASSSources) +{ + this.map = map; + this.cssEdits = cssEdits; + this.newSASSSources = newSASSSources; +} + +/** + * @param {!WebInspector.ASTService} astService + * @param {!WebInspector.ASTSourceMap} map + * @param {!Array} ranges + * @param {!Array} newTexts + * @return {!Promise} + */ +WebInspector.SASSProcessor.processCSSEdits = function(astService, map, ranges, newTexts) +{ + console.assert(ranges.length === newTexts.length); + var cssURL = map.cssURL(); + var cssText = map.cssAST().document.text; + for (var i = 0; i < ranges.length; ++i) { + var range = ranges[i]; + var edit = new WebInspector.SourceEdit(cssURL, range, newTexts[i]); + cssText = edit.applyToText(cssText); + } + return astService.parseCSS(cssURL, cssText) + .then(onCSSParsed); + + /** + * @param {!WebInspector.SASSSupport.AST} newCSSAST + * @return {!Promise} + */ + function onCSSParsed(newCSSAST) + { + //TODO(lushnikov): only diff changed styles. + var cssDiff = WebInspector.SASSSupport.diffModels(map.cssAST(), newCSSAST); + var edits = WebInspector.SASSProcessor._editsFromCSSDiff(cssDiff, map); + + // Determine AST trees which will change and clone them. + var changedURLs = new Set(edits.map(edit => edit.sassURL)); + changedURLs.add(map.cssURL()); + var clonedModels = []; + for (var url of changedURLs) + clonedModels.push(map.modelForURL(url).clone()); + + // Rebase map and edits onto a cloned AST trees. + var nodeMapping = new Map(); + var rebasedMap = /** @type {!WebInspector.ASTSourceMap} */(map.rebase(clonedModels, nodeMapping)); + console.assert(rebasedMap); + var rebasedEdits = edits.map(edit => edit.rebase(rebasedMap, nodeMapping)); + + return new WebInspector.SASSProcessor(astService, rebasedMap, rebasedEdits)._mutate(); + } +} + +/** + * @param {!WebInspector.SASSSupport.ASTDiff} cssDiff + * @param {!WebInspector.ASTSourceMap} map + * @return {!Array} + */ +WebInspector.SASSProcessor._editsFromCSSDiff = function(cssDiff, map) +{ + var T = WebInspector.SASSSupport.PropertyChangeType; + var operations = []; + for (var i = 0; i < cssDiff.changes.length; ++i) { + var change = cssDiff.changes[i]; + var operation = null; + if (change.type === T.ValueChanged || change.type === T.NameChanged) + operation = WebInspector.SASSProcessor.SetTextOperation.fromCSSChange(change, map); + else if (change.type === T.PropertyToggled) + operation = WebInspector.SASSProcessor.TogglePropertyOperation.fromCSSChange(change, map); + else if (change.type === T.PropertyRemoved) + operation = WebInspector.SASSProcessor.RemovePropertyOperation.fromCSSChange(change, map); + else if (change.type === T.PropertyAdded) + operation = WebInspector.SASSProcessor.InsertPropertiesOperation.fromCSSChange(change, map); + if (!operation) { + WebInspector.console.error("Operation ignored: " + change.type); + continue; + } + + var merged = false; + for (var j = 0; !merged && j < operations.length; ++j) + merged = operations[j].merge(operation); + if (!merged) + operations.push(operation); + } + return operations; +} + +/** + * @constructor + * @param {!WebInspector.ASTSourceMap} map + * @param {string} sassURL + */ +WebInspector.SASSProcessor.EditOperation = function(map, sassURL) +{ + this.map = map; + this.sassURL = sassURL; +} + +WebInspector.SASSProcessor.EditOperation.prototype = { + /** + * @param {!WebInspector.SASSProcessor.EditOperation} other + * @return {boolean} + */ + merge: function(other) + { + return false; + }, + + /** + * @return {!Array} + */ + perform: function() + { + return []; + }, + + /** + * @param {!WebInspector.ASTSourceMap} newMap + * @param {!Map} nodeMapping + * @return {!WebInspector.SASSProcessor.EditOperation} + */ + rebase: function(newMap, nodeMapping) + { + return this; + }, +} + +/** + * @constructor + * @extends {WebInspector.SASSProcessor.EditOperation} + * @param {!WebInspector.ASTSourceMap} map + * @param {!WebInspector.SASSSupport.TextNode} sassNode + * @param {string} newText + */ +WebInspector.SASSProcessor.SetTextOperation = function(map, sassNode, newText) +{ + WebInspector.SASSProcessor.EditOperation.call(this, map, sassNode.document.url); + this._sassNode = sassNode; + this._newText = newText; +} + +/** + * @param {!WebInspector.SASSSupport.PropertyChange} change + * @param {!WebInspector.ASTSourceMap} map + * @return {?WebInspector.SASSProcessor.SetTextOperation} + */ +WebInspector.SASSProcessor.SetTextOperation.fromCSSChange = function(change, map) +{ + var oldProperty = /** @type {!WebInspector.SASSSupport.Property} */(change.oldProperty()); + var newProperty = /** @type {!WebInspector.SASSSupport.Property} */(change.newProperty()); + console.assert(oldProperty && newProperty, "SetTextOperation must have both oldProperty and newProperty"); + var newValue = null; + var sassNode = null; + if (change.type === WebInspector.SASSSupport.PropertyChangeType.NameChanged) { + newValue = newProperty.name.text; + sassNode = map.toSASSNode(oldProperty.name); + } else { + newValue = newProperty.value.text; + sassNode = map.toSASSNode(oldProperty.value); + } + if (!sassNode) + return null; + return new WebInspector.SASSProcessor.SetTextOperation(map, sassNode, newValue); +} + +WebInspector.SASSProcessor.SetTextOperation.prototype = { + /** + * @override + * @param {!WebInspector.SASSProcessor.EditOperation} other + * @return {boolean} + */ + merge: function(other) + { + if (!(other instanceof WebInspector.SASSProcessor.SetTextOperation)) + return false; + return this._sassNode === other._sassNode; + }, + + /** + * @override + * @return {!Array} + */ + perform: function() + { + this._sassNode.setText(this._newText); + var nodes = this.map.toCSSNodes(this._sassNode); + for (var node of nodes) + node.setText(this._newText); + + var cssRules = nodes.map(textNode => textNode.parent.parent); + return cssRules; + }, + + /** + * @override + * @param {!WebInspector.ASTSourceMap} newMap + * @param {!Map} nodeMapping + * @return {!WebInspector.SASSProcessor.SetTextOperation} + */ + rebase: function(newMap, nodeMapping) + { + var sassNode = /** @type {?WebInspector.SASSSupport.TextNode} */(nodeMapping.get(this._sassNode)) || this._sassNode; + return new WebInspector.SASSProcessor.SetTextOperation(newMap, sassNode, this._newText); + }, + + __proto__: WebInspector.SASSProcessor.EditOperation.prototype +} + +/** + * @constructor + * @extends {WebInspector.SASSProcessor.EditOperation} + * @param {!WebInspector.ASTSourceMap} map + * @param {!WebInspector.SASSSupport.Property} sassProperty + * @param {boolean} newDisabled + */ +WebInspector.SASSProcessor.TogglePropertyOperation = function(map, sassProperty, newDisabled) +{ + WebInspector.SASSProcessor.EditOperation.call(this, map, sassProperty.document.url); + this._sassProperty = sassProperty; + this._newDisabled = newDisabled; +} + +/** + * @param {!WebInspector.SASSSupport.PropertyChange} change + * @param {!WebInspector.ASTSourceMap} map + * @return {?WebInspector.SASSProcessor.TogglePropertyOperation} + */ +WebInspector.SASSProcessor.TogglePropertyOperation.fromCSSChange = function(change, map) +{ + var oldCSSProperty = /** @type {!WebInspector.SASSSupport.Property} */(change.oldProperty()); + console.assert(oldCSSProperty, "TogglePropertyOperation must have old CSS property"); + var sassProperty = map.toSASSProperty(oldCSSProperty); + if (!sassProperty) + return null; + var newDisabled = change.newProperty().disabled; + return new WebInspector.SASSProcessor.TogglePropertyOperation(map, sassProperty, newDisabled); +} + +WebInspector.SASSProcessor.TogglePropertyOperation.prototype = { + /** + * @override + * @param {!WebInspector.SASSProcessor.EditOperation} other + * @return {boolean} + */ + merge: function(other) + { + if (!(other instanceof WebInspector.SASSProcessor.TogglePropertyOperation)) + return false; + return this._sassProperty === other._sassProperty; + }, + + /** + * @override + * @return {!Array} + */ + perform: function() + { + this._sassProperty.setDisabled(this._newDisabled); + var cssProperties = this.map.toCSSProperties(this._sassProperty); + for (var property of cssProperties) + property.setDisabled(this._newDisabled); + + var cssRules = cssProperties.map(property => property.parent); + return cssRules; + }, + + /** + * @override + * @param {!WebInspector.ASTSourceMap} newMap + * @param {!Map} nodeMapping + * @return {!WebInspector.SASSProcessor.TogglePropertyOperation} + */ + rebase: function(newMap, nodeMapping) + { + var sassProperty = /** @type {?WebInspector.SASSSupport.Property} */(nodeMapping.get(this._sassProperty)) || this._sassProperty; + return new WebInspector.SASSProcessor.TogglePropertyOperation(newMap, sassProperty, this._newDisabled); + }, + + __proto__: WebInspector.SASSProcessor.EditOperation.prototype +} + +/** + * @constructor + * @extends {WebInspector.SASSProcessor.EditOperation} + * @param {!WebInspector.ASTSourceMap} map + * @param {!WebInspector.SASSSupport.Property} sassProperty + */ +WebInspector.SASSProcessor.RemovePropertyOperation = function(map, sassProperty) +{ + WebInspector.SASSProcessor.EditOperation.call(this, map, sassProperty.document.url); + this._sassProperty = sassProperty; +} + +/** + * @param {!WebInspector.SASSSupport.PropertyChange} change + * @param {!WebInspector.ASTSourceMap} map + * @return {?WebInspector.SASSProcessor.RemovePropertyOperation} + */ +WebInspector.SASSProcessor.RemovePropertyOperation.fromCSSChange = function(change, map) +{ + var removedProperty = /** @type {!WebInspector.SASSSupport.Property} */(change.oldProperty()); + console.assert(removedProperty, "RemovePropertyOperation must have removed CSS property"); + var sassProperty = map.toSASSProperty(removedProperty); + if (!sassProperty) + return null; + return new WebInspector.SASSProcessor.RemovePropertyOperation(map, sassProperty); +} + +WebInspector.SASSProcessor.RemovePropertyOperation.prototype = { + /** + * @override + * @param {!WebInspector.SASSProcessor.EditOperation} other + * @return {boolean} + */ + merge: function(other) + { + if (!(other instanceof WebInspector.SASSProcessor.RemovePropertyOperation)) + return false; + return this._sassProperty === other._sassProperty; + }, + + /** + * @override + * @return {!Array} + */ + perform: function() + { + var cssProperties = this.map.toCSSProperties(this._sassProperty); + var cssRules = cssProperties.map(property => property.parent); + this._sassProperty.remove(); + for (var cssProperty of cssProperties) { + cssProperty.remove(); + this.map.unmapCssFromSass(cssProperty.name, this._sassProperty.name); + this.map.unmapCssFromSass(cssProperty.value, this._sassProperty.value); + } + + return cssRules; + }, + + /** + * @override + * @param {!WebInspector.ASTSourceMap} newMap + * @param {!Map} nodeMapping + * @return {!WebInspector.SASSProcessor.RemovePropertyOperation} + */ + rebase: function(newMap, nodeMapping) + { + var sassProperty = /** @type {?WebInspector.SASSSupport.Property} */(nodeMapping.get(this._sassProperty)) || this._sassProperty; + return new WebInspector.SASSProcessor.RemovePropertyOperation(newMap, sassProperty); + }, + + __proto__: WebInspector.SASSProcessor.EditOperation.prototype +} + +/** + * @constructor + * @extends {WebInspector.SASSProcessor.EditOperation} + * @param {!WebInspector.ASTSourceMap} map + * @param {!WebInspector.SASSSupport.Property} sassAnchor + * @param {boolean} insertBefore + * @param {!Array} propertyNames + * @param {!Array} propertyValues + * @param {!Array} disabledStates + */ +WebInspector.SASSProcessor.InsertPropertiesOperation = function(map, sassAnchor, insertBefore, propertyNames, propertyValues, disabledStates) +{ + console.assert(propertyNames.length === propertyValues.length && propertyValues.length === disabledStates.length); + WebInspector.SASSProcessor.EditOperation.call(this, map, sassAnchor.document.url); + this._sassAnchor = sassAnchor; + this._insertBefore = insertBefore; + this._nameTexts = propertyNames; + this._valueTexts = propertyValues; + this._disabledStates = disabledStates; +} + +/** + * @param {!WebInspector.SASSSupport.PropertyChange} change + * @param {!WebInspector.ASTSourceMap} map + * @return {?WebInspector.SASSProcessor.InsertPropertiesOperation} + */ +WebInspector.SASSProcessor.InsertPropertiesOperation.fromCSSChange = function(change, map) +{ + var insertBefore = false; + var cssAnchor = null; + var sassAnchor = null; + if (change.oldPropertyIndex) { + cssAnchor = change.oldRule.properties[change.oldPropertyIndex - 1].name; + sassAnchor = map.toSASSNode(cssAnchor); + } else { + insertBefore = true; + cssAnchor = change.oldRule.properties[0].name; + sassAnchor = map.toSASSNode(cssAnchor); + } + if (!sassAnchor) + return null; + var insertedProperty = /** @type {!WebInspector.SASSSupport.Property} */(change.newProperty()); + console.assert(insertedProperty, "InsertPropertiesOperation must have inserted CSS property"); + var names = [insertedProperty.name.text]; + var values = [insertedProperty.value.text]; + var disabledStates = [insertedProperty.disabled]; + return new WebInspector.SASSProcessor.InsertPropertiesOperation(map, sassAnchor.parent, insertBefore, names, values, disabledStates); +} + +WebInspector.SASSProcessor.InsertPropertiesOperation.prototype = { + /** + * @override + * @param {!WebInspector.SASSProcessor.EditOperation} other + * @return {boolean} + */ + merge: function(other) + { + if (!(other instanceof WebInspector.SASSProcessor.InsertPropertiesOperation)) + return false; + if (this._sassAnchor !== other._sassAnchor || this._insertBefore !== other._insertBefore) + return false; + var names = new Set(this._nameTexts); + for (var i = 0; i < other._nameTexts.length; ++i) { + var nameText = other._nameTexts[i]; + if (names.has(nameText)) + continue; + this._nameTexts.push(nameText); + this._valueTexts.push(other._valueTexts[i]); + this._disabledStates.push(other._disabledStates[i]); + } + return true; + }, + + /** + * @override + * @return {!Array} + */ + perform: function() + { + var cssRules = []; + var sassRule = this._sassAnchor.parent; + var newSASSProperties = sassRule.insertProperties(this._nameTexts, this._valueTexts, this._disabledStates, this._sassAnchor, this._insertBefore); + var cssAnchors = this.map.toCSSProperties(this._sassAnchor); + for (var cssAnchor of cssAnchors) { + var cssRule = cssAnchor.parent; + cssRules.push(cssRule); + var newCSSProperties = cssRule.insertProperties(this._nameTexts, this._valueTexts, this._disabledStates, cssAnchor, this._insertBefore); + for (var i = 0; i < newCSSProperties.length; ++i) { + this.map.mapCssToSass(newCSSProperties[i].name, newSASSProperties[i].name); + this.map.mapCssToSass(newCSSProperties[i].value, newSASSProperties[i].value); + } + } + return cssRules; + }, + + /** + * @override + * @param {!WebInspector.ASTSourceMap} newMap + * @param {!Map} nodeMapping + * @return {!WebInspector.SASSProcessor.InsertPropertiesOperation} + */ + rebase: function(newMap, nodeMapping) + { + var sassAnchor = /** @type {?WebInspector.SASSSupport.Property} */(nodeMapping.get(this._sassAnchor)) || this._sassAnchor; + return new WebInspector.SASSProcessor.InsertPropertiesOperation(newMap, sassAnchor, this._insertBefore, this._nameTexts, this._valueTexts, this._disabledStates); + }, + + __proto__: WebInspector.SASSProcessor.EditOperation.prototype +} diff --git a/front_end/sass/SASSSupport.js b/front_end/sass/SASSSupport.js index 0ed1943c08..bb387c2e91 100644 --- a/front_end/sass/SASSSupport.js +++ b/front_end/sass/SASSSupport.js @@ -5,14 +5,14 @@ WebInspector.SASSSupport = {} /** - * @param {!WebInspector.CSSParser} parser + * @param {!WebInspector.CSSParserService} cssParserService * @param {string} url * @param {string} text * @return {!Promise} */ -WebInspector.SASSSupport.parseCSS = function(parser, url, text) +WebInspector.SASSSupport.parseCSS = function(cssParserService, url, text) { - return parser.parsePromise(text) + return cssParserService.parseCSS(text) .then(onParsed); /** @@ -35,27 +35,27 @@ WebInspector.SASSSupport.parseCSS = function(parser, url, text) var property = new WebInspector.SASSSupport.Property(document, name, value, WebInspector.TextRange.fromObject(cssProperty.range), !!cssProperty.disabled); properties.push(property); } - rules.push(new WebInspector.SASSSupport.Rule(document, rule.selectorText, properties)); + rules.push(new WebInspector.SASSSupport.Rule(document, rule.selectorText, WebInspector.TextRange.fromObject(rule.styleRange), properties)); } return new WebInspector.SASSSupport.AST(document, rules); } } /** + * @param {!WebInspector.TokenizerFactory} tokenizerFactory * @param {string} url * @param {string} text - * @param {!WebInspector.TokenizerFactory} tokenizerFactory * @return {!WebInspector.SASSSupport.AST} */ -WebInspector.SASSSupport.parseSCSS = function(url, text, tokenizerFactory) +WebInspector.SASSSupport.parseSCSS = function(tokenizerFactory, url, text) { var document = new WebInspector.SASSSupport.ASTDocument(url, text); var result = WebInspector.SASSSupport._innerParseSCSS(document, tokenizerFactory); var rules = [ - new WebInspector.SASSSupport.Rule(document, "variables", result.variables), - new WebInspector.SASSSupport.Rule(document, "properties", result.properties), - new WebInspector.SASSSupport.Rule(document, "mixins", result.mixins) + new WebInspector.SASSSupport.Rule(document, "variables", WebInspector.TextRange.createFromLocation(0, 0), result.variables), + new WebInspector.SASSSupport.Rule(document, "properties", WebInspector.TextRange.createFromLocation(0, 0), result.properties), + new WebInspector.SASSSupport.Rule(document, "mixins", WebInspector.TextRange.createFromLocation(0, 0), result.mixins) ]; return new WebInspector.SASSSupport.AST(document, rules); @@ -70,7 +70,7 @@ WebInspector.SASSSupport.SCSSParserStates = { VariableValue: "VariableValue", MixinName: "MixinName", MixinValue: "MixinValue", - Media: "Media", + Media: "Media" } /** @@ -139,7 +139,9 @@ WebInspector.SASSSupport._innerParseSCSS = function(document, tokenizerFactory) } break; case States.VariableName: - if (tokenValue === ")" && tokenType === UndefTokenType) { + if (tokenValue === "}" && tokenType === UndefTokenType) { + state = States.Initial; + } else if (tokenValue === ")" && tokenType === UndefTokenType) { state = States.Initial; } else if (tokenValue === ":" && tokenType === UndefTokenType) { state = States.VariableValue; @@ -253,6 +255,14 @@ WebInspector.SASSSupport.ASTDocument.prototype = { return new WebInspector.SASSSupport.ASTDocument(this.url, this.text); }, + /** + * @return {boolean} + */ + hasChanged: function() + { + return !!this.edits.length; + }, + /** * @return {string} */ @@ -312,7 +322,7 @@ WebInspector.SASSSupport.TextNode.prototype = { if (this.text === newText) return; this.text = newText; - this.document.edits.push(new WebInspector.SourceEdit(this.document.url, this.range, this.text, newText)); + this.document.edits.push(new WebInspector.SourceEdit(this.document.url, this.range, newText)); }, /** @@ -324,6 +334,20 @@ WebInspector.SASSSupport.TextNode.prototype = { return new WebInspector.SASSSupport.TextNode(document, this.text, this.range.clone()); }, + /** + * @param {!WebInspector.SASSSupport.TextNode} other + * @param {!Map=} outNodeMapping + * @return {boolean} + */ + match: function(other, outNodeMapping) + { + if (this.text.trim() !== other.text.trim()) + return false; + if (outNodeMapping) + outNodeMapping.set(this, other); + return true; + }, + __proto__: WebInspector.SASSSupport.Node.prototype } @@ -367,6 +391,20 @@ WebInspector.SASSSupport.Property.prototype = { callback(this.value); }, + /** + * @param {!WebInspector.SASSSupport.Property} other + * @param {!Map=} outNodeMapping + * @return {boolean} + */ + match: function(other, outNodeMapping) + { + if (this.disabled !== other.disabled) + return false; + if (outNodeMapping) + outNodeMapping.set(this, other); + return this.name.match(other.name, outNodeMapping) && this.value.match(other.value, outNodeMapping); + }, + /** * @param {boolean} disabled */ @@ -377,17 +415,17 @@ WebInspector.SASSSupport.Property.prototype = { this.disabled = disabled; if (disabled) { var oldRange1 = WebInspector.TextRange.createFromLocation(this.range.startLine, this.range.startColumn); - var edit1 = new WebInspector.SourceEdit(this.document.url, oldRange1, "", "/* "); + var edit1 = new WebInspector.SourceEdit(this.document.url, oldRange1, "/* "); var oldRange2 = WebInspector.TextRange.createFromLocation(this.range.endLine, this.range.endColumn); - var edit2 = new WebInspector.SourceEdit(this.document.url, oldRange2, "", " */"); + var edit2 = new WebInspector.SourceEdit(this.document.url, oldRange2, " */"); this.document.edits.push(edit1, edit2); return; } var oldRange1 = new WebInspector.TextRange(this.range.startLine, this.range.startColumn, this.range.startLine, this.name.range.startColumn); var text = this.document.text; - var edit1 = new WebInspector.SourceEdit(this.document.url, oldRange1, oldRange1.extract(text), ""); + var edit1 = new WebInspector.SourceEdit(this.document.url, oldRange1, ""); var oldRange2 = new WebInspector.TextRange(this.range.endLine, this.range.endColumn - 2, this.range.endLine, this.range.endColumn); - var edit2 = new WebInspector.SourceEdit(this.document.url, oldRange2, "*/", ""); + var edit2 = new WebInspector.SourceEdit(this.document.url, oldRange2, ""); this.document.edits.push(edit1, edit2); }, @@ -405,7 +443,7 @@ WebInspector.SASSSupport.Property.prototype = { oldRange = lineRange; else oldRange = this.range; - this.document.edits.push(new WebInspector.SourceEdit(this.document.url, oldRange, oldRange.extract(this.document.text), "")); + this.document.edits.push(new WebInspector.SourceEdit(this.document.url, oldRange, "")); }, __proto__: WebInspector.SASSSupport.Node.prototype @@ -416,13 +454,15 @@ WebInspector.SASSSupport.Property.prototype = { * @extends {WebInspector.SASSSupport.Node} * @param {!WebInspector.SASSSupport.ASTDocument} document * @param {string} selector + * @param {!WebInspector.TextRange} styleRange * @param {!Array} properties */ -WebInspector.SASSSupport.Rule = function(document, selector, properties) +WebInspector.SASSSupport.Rule = function(document, selector, styleRange, properties) { WebInspector.SASSSupport.Node.call(this, document); this.selector = selector; this.properties = properties; + this.styleRange = styleRange; for (var i = 0; i < this.properties.length; ++i) this.properties[i].parent = this; @@ -439,7 +479,7 @@ WebInspector.SASSSupport.Rule.prototype = { var properties = []; for (var i = 0; i < this.properties.length; ++i) properties.push(this.properties[i].clone(document)); - return new WebInspector.SASSSupport.Rule(document, this.selector, properties); + return new WebInspector.SASSSupport.Rule(document, this.selector, this.styleRange.clone(), properties); }, /** @@ -452,36 +492,77 @@ WebInspector.SASSSupport.Rule.prototype = { this.properties[i].visit(callback); }, + /** + * @param {!WebInspector.SASSSupport.Rule} other + * @param {!Map=} outNodeMapping + * @return {boolean} + */ + match: function(other, outNodeMapping) + { + if (this.selector !== other.selector) + return false; + if (this.properties.length !== other.properties.length) + return false; + if (outNodeMapping) + outNodeMapping.set(this, other); + var result = true; + for (var i = 0; result && i < this.properties.length; ++i) + result = result && this.properties[i].match(other.properties[i], outNodeMapping); + return result; + }, + _addTrailingSemicolon: function() { if (this._hasTrailingSemicolon || !this.properties) return; this._hasTrailingSemicolon = true; - this.document.edits.push(new WebInspector.SourceEdit(this.document.url, this.properties.peekLast().range.collapseToEnd(), "", ";")) + this.document.edits.push(new WebInspector.SourceEdit(this.document.url, this.properties.peekLast().range.collapseToEnd(), ";")) }, /** - * @param {string} nameText - * @param {string} valueText - * @param {boolean} disabled + * @param {!Array} nameTexts + * @param {!Array} valueTexts + * @param {!Array} disabledStates * @param {!WebInspector.SASSSupport.Property} anchorProperty * @param {boolean} insertBefore - * @return {!WebInspector.SASSSupport.Property} + * @return {!Array} */ - insertProperty: function(nameText, valueText, disabled, anchorProperty, insertBefore) + insertProperties: function(nameTexts, valueTexts, disabledStates, anchorProperty, insertBefore) { console.assert(this.properties.length, "Cannot insert in empty rule."); + console.assert(nameTexts.length === valueTexts.length && valueTexts.length === disabledStates.length, "Input array should be of the same size."); this._addTrailingSemicolon(); + var newProperties = []; + var index = this.properties.indexOf(anchorProperty); + for (var i = 0; i < nameTexts.length; ++i) { + var nameText = nameTexts[i]; + var valueText = valueTexts[i]; + var disabled = disabledStates[i]; + this.document.edits.push(this._insertPropertyEdit(nameText, valueText, disabled, anchorProperty, insertBefore)); - var name = new WebInspector.SASSSupport.TextNode(this.document, nameText, WebInspector.TextRange.createFromLocation(10, 0)); - var value = new WebInspector.SASSSupport.TextNode(this.document, valueText, WebInspector.TextRange.createFromLocation(10, 0)); - var newProperty = new WebInspector.SASSSupport.Property(this.document, name, value, WebInspector.TextRange.createFromLocation(10, 0), disabled); + var name = new WebInspector.SASSSupport.TextNode(this.document, nameText, WebInspector.TextRange.createFromLocation(0, 0)); + var value = new WebInspector.SASSSupport.TextNode(this.document, valueText, WebInspector.TextRange.createFromLocation(0, 0)); + var newProperty = new WebInspector.SASSSupport.Property(this.document, name, value, WebInspector.TextRange.createFromLocation(0, 0), disabled); - var index = this.properties.indexOf(anchorProperty); - this.properties.splice(insertBefore ? index : index + 1, 0, newProperty); - newProperty.parent = this; + this.properties.splice(insertBefore ? index + i : index + i + 1, 0, newProperty); + newProperty.parent = this; + + newProperties.push(newProperty); + } + return newProperties; + }, + /** + * @param {string} nameText + * @param {string} valueText + * @param {boolean} disabled + * @param {!WebInspector.SASSSupport.Property} anchorProperty + * @param {boolean} insertBefore + * @return {!WebInspector.SourceEdit} + */ + _insertPropertyEdit: function(nameText, valueText, disabled, anchorProperty, insertBefore) + { var oldRange = insertBefore ? anchorProperty.range.collapseToStart() : anchorProperty.range.collapseToEnd(); var indent = (new WebInspector.TextRange(anchorProperty.range.startLine, 0, anchorProperty.range.startLine, anchorProperty.range.startColumn)).extract(this.document.text); if (!/^\s+$/.test(indent)) indent = ""; @@ -491,12 +572,11 @@ WebInspector.SASSSupport.Rule.prototype = { var rightComment = disabled ? " */" : ""; if (insertBefore) { - newText = String.sprintf("%s%s: %s;%s\n%s", leftComment, newProperty.name.text, newProperty.value.text, rightComment, indent); + newText = String.sprintf("%s%s: %s;%s\n%s", leftComment, nameText, valueText, rightComment, indent); } else { - newText = String.sprintf("\n%s%s%s: %s;%s", indent, leftComment, newProperty.name.text, newProperty.value.text, rightComment); + newText = String.sprintf("\n%s%s%s: %s;%s", indent, leftComment, nameText, valueText, rightComment); } - this.document.edits.push(new WebInspector.SourceEdit(this.document.url, oldRange, "", newText)); - return newProperty; + return new WebInspector.SourceEdit(this.document.url, oldRange, newText); }, __proto__: WebInspector.SASSSupport.Node.prototype @@ -529,6 +609,25 @@ WebInspector.SASSSupport.AST.prototype = { return new WebInspector.SASSSupport.AST(document, rules); }, + /** + * @param {!WebInspector.SASSSupport.AST} other + * @param {!Map=} outNodeMapping + * @return {boolean} + */ + match: function(other, outNodeMapping) + { + if (other.document.url !== this.document.url) + return false; + if (other.rules.length !== this.rules.length) + return false; + if (outNodeMapping) + outNodeMapping.set(this, other); + var result = true; + for (var i = 0; result && i < this.rules.length; ++i) + result = result && this.rules[i].match(other.rules[i], outNodeMapping); + return result; + }, + /** * @param {function(!WebInspector.SASSSupport.Node)} callback */ @@ -591,6 +690,24 @@ WebInspector.SASSSupport.PropertyChange = function(type, oldRule, newRule, oldPr this.newPropertyIndex = newPropertyIndex; } +WebInspector.SASSSupport.PropertyChange.prototype = { + /** + * @return {?WebInspector.SASSSupport.Property} + */ + oldProperty: function() + { + return this.oldRule.properties[this.oldPropertyIndex] || null; + }, + + /** + * @return {?WebInspector.SASSSupport.Property} + */ + newProperty: function() + { + return this.newRule.properties[this.newPropertyIndex] || null; + } +} + /** * @constructor * @param {string} url diff --git a/front_end/sass/SASSWorkspaceAdapter.js b/front_end/sass/SASSWorkspaceAdapter.js deleted file mode 100644 index 5dc3292aa3..0000000000 --- a/front_end/sass/SASSWorkspaceAdapter.js +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * @constructor - * @param {!WebInspector.CSSStyleModel} cssModel - * @param {!WebInspector.Workspace} workspace - * @param {!WebInspector.NetworkMapping} networkMapping - */ -WebInspector.SASSWorkspaceAdapter = function(cssModel, workspace, networkMapping) -{ - this._workspace = workspace; - this._networkMapping = networkMapping; - this._cssModel = cssModel; - - /** @type {!Map} */ - this._versions = new Map(); - /** @type {!Map>} */ - this._awaitingPromises = new Map(); - /** @type {!Map} */ - this._awaitingFulfills = new Map(); - - /** @type {!Multimap} */ - this._urlToTrackers = new Multimap(); - /** @type {!Set} */ - this._cssURLs = new Set(); - - this._eventListeners = [ - this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this), - this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this), - this._workspace.addEventListener(WebInspector.Workspace.Events.WorkingCopyChanged, this._uiSourceCodeChanged, this), - this._workspace.addEventListener(WebInspector.Workspace.Events.WorkingCopyCommitted, this._uiSourceCodeChanged, this), - this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this), - this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this), - this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this) - ]; -} - -/** - * @constructor - * @param {string} url - * @param {number} version - * @param {string} text - */ -WebInspector.SASSWorkspaceAdapter.ContentResponse = function(url, version, text) -{ - this.url = url; - this.version = version; - this.text = text; -} - -WebInspector.SASSWorkspaceAdapter.prototype = { - /** - * @param {!WebInspector.SourceMap} sourceMap - * @return {!WebInspector.SourceMapTracker} - */ - trackSources: function(sourceMap) - { - var cssURL = sourceMap.compiledURL(); - this._cssURLs.add(cssURL); - - var allSources = new Set(sourceMap.sources().concat(cssURL)); - for (var sourceURL of allSources) { - if (this._versions.has(sourceURL)) - continue; - this._versions.set(sourceURL, 1); - var promise = new Promise(fulfill => this._awaitingFulfills.set(sourceURL, fulfill)); - this._awaitingPromises.set(sourceURL, promise); - var contentProvider = sourceURL === cssURL ? this._headersForURL(sourceURL).peekLast() : this._sassUISourceCode(sourceURL); - if (contentProvider) - this._contentProviderAdded(sourceURL); - } - - var tracker = new WebInspector.SourceMapTracker(this, sourceMap); - for (var sourceURL of tracker.allURLs()) - this._urlToTrackers.set(sourceURL, tracker); - return tracker; - }, - - /** - * @param {!WebInspector.SourceMapTracker} tracker - */ - _stopTrackSources: function(tracker) - { - for (var sourceURL of tracker.allURLs()) { - this._urlToTrackers.remove(sourceURL, tracker); - if (!this._urlToTrackers.has(sourceURL)) { - this._awaitingFulfills.get(sourceURL).call(null, false); - this._awaitingFulfills.delete(sourceURL); - this._awaitingPromises.delete(sourceURL); - this._versions.delete(sourceURL); - this._cssURLs.delete(sourceURL); - } - } - }, - - /** - * @param {string} url - * @return {?WebInspector.UISourceCode} - */ - _sassUISourceCode: function(url) - { - return this._networkMapping.uiSourceCodeForURLForAnyTarget(url); - }, - - /** - * @param {string} url - * @return {!Array} - */ - _headersForURL: function(url) - { - return this._cssModel.styleSheetIdsForURL(url) - .map(styleSheetId => this._cssModel.styleSheetHeaderForId(styleSheetId)); - }, - - /** - * @param {string} url - */ - _contentProviderAdded: function(url) - { - this._awaitingFulfills.get(url).call(null, true); - }, - - /** - * @param {string} url - */ - _contentProviderRemoved: function(url) - { - var trackers = new Set(this._urlToTrackers.get(url)); - for (var tracker of trackers) - tracker.dispose(); - }, - - /** - * @param {string} url - * @return {boolean} - */ - _isSASSURL: function(url) - { - return this._versions.has(url) && !this._cssURLs.has(url); - }, - - /** - * @param {!WebInspector.Event} event - */ - _uiSourceCodeAdded: function(event) - { - var uiSourceCode = /** @type {!WebInspector.UISourceCode} */(event.data); - var url = this._networkMapping.networkURL(uiSourceCode); - if (!this._isSASSURL(url)) - return; - this._contentProviderAdded(url); - }, - - /** - * @param {!WebInspector.Event} event - */ - _uiSourceCodeRemoved: function(event) - { - var uiSourceCode = /** @type {!WebInspector.UISourceCode} */(event.data); - var url = this._networkMapping.networkURL(uiSourceCode); - if (!this._isSASSURL(url)) - return; - this._contentProviderRemoved(url); - }, - - /** - * @param {!WebInspector.Event} event - */ - _styleSheetAdded: function(event) - { - var styleSheetHeader = /** @type {!WebInspector.CSSStyleSheetHeader} */(event.data); - var url = styleSheetHeader.sourceURL; - if (!this._cssURLs.has(url)) - return; - this._contentProviderAdded(url); - }, - - /** - * @param {!WebInspector.Event} event - */ - _styleSheetRemoved: function(event) - { - var styleSheetHeader = /** @type {!WebInspector.CSSStyleSheetHeader} */(event.data); - var url = styleSheetHeader.sourceURL; - if (!this._cssURLs.has(url)) - return; - var headers = this._headersForURL(url); - if (headers.length) - return; - this._contentProviderRemoved(url); - }, - - /** - * @param {!WebInspector.Event} event - */ - _uiSourceCodeChanged: function(event) - { - var uiSourceCode = /** @type {!WebInspector.UISourceCode} */(event.data.uiSourceCode); - var url = this._networkMapping.networkURL(uiSourceCode); - if (!this._isSASSURL(url)) - return; - this._newContentAvailable(url); - }, - - /** - * @param {!WebInspector.Event} event - */ - _styleSheetChanged: function(event) - { - var styleSheetId = /** @type {!CSSAgent.StyleSheetId} */(event.data.styleSheetId); - var styleSheetHeader = this._cssModel.styleSheetHeaderForId(styleSheetId); - var url = styleSheetHeader.sourceURL; - if (!this._cssURLs.has(url)) - return; - this._newContentAvailable(url); - }, - - /** - * @param {string} url - */ - _newContentAvailable: function(url) - { - console.assert(this._versions.has(url), "The '" + url + "' is not tracked.") - var newVersion = this._versions.get(url) + 1; - this._versions.set(url, newVersion); - for (var tracker of this._urlToTrackers.get(url)) - tracker._newContentAvailable(url, newVersion); - }, - - /** - * @param {string} url - * @return {number} - */ - _urlVersion: function(url) - { - var version = this._versions.get(url); - console.assert(version, "The '" + url + "' is not tracked.") - return version || 0; - }, - - /** - * @param {string} url - * @return {!Promise} - */ - _getContent: function(url) - { - console.assert(this._awaitingPromises.has(url), "The '" + url + "' is not tracked.") - return this._awaitingPromises.get(url) - .then(onContentProviderResolved.bind(this)); - - /** - * @param {boolean} success - * @return {!Promise} - * @this {WebInspector.SASSWorkspaceAdapter} - */ - function onContentProviderResolved(success) - { - if (!success) - return Promise.resolve(/** @type {?WebInspector.SASSWorkspaceAdapter.ContentResponse} */(null)); - var contentProvider = this._cssURLs.has(url) ? this._headersForURL(url).peekLast() : this._sassUISourceCode(url); - if (!contentProvider) - return Promise.resolve(/** @type {?WebInspector.SASSWorkspaceAdapter.ContentResponse} */(null)); - return contentProvider.requestContent() - .then(text => new WebInspector.SASSWorkspaceAdapter.ContentResponse(url, /** @type {number} */(this._versions.get(url)), text || "")); - } - }, - - /** - * @param {string} url - * @param {string} text - * @return {?WebInspector.SASSWorkspaceAdapter.ContentResponse} - */ - _setSASSText: function(url, text) - { - console.assert(this._isSASSURL(url), "The url '" + url + "' should be a tracked SASS url"); - var uiSourceCode = this._sassUISourceCode(url); - if (!uiSourceCode) - return null; - setImmediate(() => uiSourceCode.addRevision(text)); - var futureVersion = this._versions.get(url) + 1; - return new WebInspector.SASSWorkspaceAdapter.ContentResponse(url, futureVersion, text); - }, - - /** - * @param {string} url - * @param {string} text - * @param {!Array} cssEdits - * @return {?WebInspector.SASSWorkspaceAdapter.ContentResponse} - */ - _setCSSText: function(url, text, cssEdits) - { - console.assert(this._cssURLs.has(url), "The url '" + url + "' should be a tracked CSS url"); - var headers = this._headersForURL(url); - if (!headers.length) - return null; - for (var i = 0; i < headers.length; ++i) - this._cssModel.setStyleSheetText(headers[i].id, text, true); - for (var i = cssEdits.length - 1; i >= 0; --i) { - var edit = cssEdits[i]; - var oldRange = edit.oldRange; - var newRange = edit.newRange(); - for (var j = 0; j < headers.length; ++j) { - this._cssModel.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.ExternalRangeEdit, { - styleSheetId: headers[j].id, - oldRange: oldRange, - newRange: newRange - }); - } - } - var futureVersion = this._versions.get(url) + headers.length; - return new WebInspector.SASSWorkspaceAdapter.ContentResponse(url, futureVersion, text); - } -} - -/** - * @constructor - * @extends {WebInspector.Object} - * @param {!WebInspector.SASSWorkspaceAdapter} adapter - * @param {!WebInspector.SourceMap} sourceMap - */ -WebInspector.SourceMapTracker = function(adapter, sourceMap) -{ - WebInspector.Object.call(this); - this._adapter = adapter; - this._sourceMap = sourceMap; - this._cssURL = sourceMap.compiledURL(); - this._sassURLs = sourceMap.sources().slice(); - this._allURLs = this._sassURLs.concat(this._cssURL); - this._terminated = false; - this._versions = new Map(); - for (var url of this._allURLs) - this._versions.set(url, adapter._urlVersion(url)); -} - -/** @enum {string} */ -WebInspector.SourceMapTracker.Events = { - SourceChanged: "SourceChanged", - TrackingStopped: "TrackingStopped" -} - -WebInspector.SourceMapTracker.prototype = { - /** - * @return {!WebInspector.SourceMap} - */ - sourceMap: function() - { - return this._sourceMap; - }, - - /** - * @return {!Array} - */ - allURLs: function() - { - return this._allURLs; - }, - - /** - * @return {string} - */ - cssURL: function() - { - return this._cssURL; - }, - - /** - * @return {!Array} - */ - sassURLs: function() - { - return this._sassURLs; - }, - - /** - * @return {boolean} - */ - isOutdated: function() - { - if (this._terminated) - return true; - for (var url of this._allURLs) { - if (this._adapter._urlVersion(url) > this._versions.get(url)) - return true; - } - return false; - }, - - /** - * @param {string} text - * @param {!Array} edits - * @return {boolean} - */ - setCSSText: function(text, edits) - { - if (this._terminated || this.isOutdated()) - return false; - var result = this._adapter._setCSSText(this._cssURL, text, edits); - this._handleContentResponse(result); - return !!result; - }, - - /** - * @param {string} url - * @param {string} text - * @return {boolean} - */ - setSASSText: function(url, text) - { - if (this._terminated || this.isOutdated()) - return false; - var result = this._adapter._setSASSText(url, text); - this._handleContentResponse(result); - return !!result; - }, - - /** - * @param {?WebInspector.SASSWorkspaceAdapter.ContentResponse} contentResponse - * @return {?string} - */ - _handleContentResponse: function(contentResponse) - { - if (!contentResponse) - return null; - this._versions.set(contentResponse.url, contentResponse.version); - return contentResponse.text; - }, - - /** - * @param {string} url - * @return {!Promise} - */ - content: function(url) - { - return this._adapter._getContent(url) - .then(this._handleContentResponse.bind(this)) - .then(text => text || ""); - }, - - dispose: function() - { - if (this._terminated) - return; - this._terminated = true; - this._adapter._stopTrackSources(this); - this.dispatchEventToListeners(WebInspector.SourceMapTracker.Events.TrackingStopped); - }, - - /** - * @param {string} url - * @param {number} newVersion - */ - _newContentAvailable: function(url, newVersion) - { - if (this._versions.get(url) < newVersion) - this.dispatchEventToListeners(WebInspector.SourceMapTracker.Events.SourceChanged, url); - }, - - __proto__: WebInspector.Object.prototype -} diff --git a/front_end/sass/module.json b/front_end/sass/module.json index d4704ba545..317d348fab 100644 --- a/front_end/sass/module.json +++ b/front_end/sass/module.json @@ -1,8 +1,9 @@ { - "dependencies": ["platform", "common", "diff", "sdk", "workspace", "bindings"], + "dependencies": ["platform", "common", "diff", "sdk"], "scripts": [ "SASSSupport.js", - "SASSWorkspaceAdapter.js", - "SASSLiveSourceMap.js" + "ASTService.js", + "SASSProcessor.js", + "ASTSourceMap.js" ] } diff --git a/front_end/script_formatter_worker/ScriptFormatterWorker.js b/front_end/script_formatter_worker/ScriptFormatterWorker.js index 9af72ef5fe..df531442b2 100644 --- a/front_end/script_formatter_worker/ScriptFormatterWorker.js +++ b/front_end/script_formatter_worker/ScriptFormatterWorker.js @@ -230,6 +230,7 @@ FormatterWorker._innerParseCSS = function(text, chunkCallback) case FormatterWorker.CSSParserStates.Selector: if (tokenValue === "{" && tokenType === UndefTokenType) { rule.selectorText = rule.selectorText.trim(); + rule.styleRange = createRange(lineNumber, newColumn); state = FormatterWorker.CSSParserStates.Style; } else { rule.selectorText += tokenValue; @@ -254,6 +255,8 @@ FormatterWorker._innerParseCSS = function(text, chunkCallback) }; state = FormatterWorker.CSSParserStates.PropertyName; } else if (tokenValue === "}" && tokenType === UndefTokenType) { + rule.styleRange.endLine = lineNumber; + rule.styleRange.endColumn = column; rules.push(rule); state = FormatterWorker.CSSParserStates.Initial; } else if (tokenType["comment"]) { @@ -304,6 +307,8 @@ FormatterWorker._innerParseCSS = function(text, chunkCallback) property.range.endColumn = tokenValue === ";" ? newColumn : column; rule.properties.push(property); if (tokenValue === "}") { + rule.styleRange.endLine = lineNumber; + rule.styleRange.endColumn = column; rules.push(rule); state = FormatterWorker.CSSParserStates.Initial; } else { diff --git a/front_end/sdk/CSSParser.js b/front_end/sdk/CSSParser.js index 63376821df..3096a7ef74 100644 --- a/front_end/sdk/CSSParser.js +++ b/front_end/sdk/CSSParser.js @@ -66,6 +66,7 @@ WebInspector.CSSParser.prototype = { if (this._worker) { this._worker.terminate(); delete this._worker; + this._runFinishedCallback([]); } }, @@ -115,8 +116,18 @@ WebInspector.CSSParser.prototype = { _onFinishedParsing: function() { this._unlock(); - if (this._finishedCallback) - this._finishedCallback(this._rules); + this._runFinishedCallback(this._rules); + }, + + /** + * @param {!Array} rules + */ + _runFinishedCallback: function(rules) + { + var callback = this._finishedCallback; + delete this._finishedCallback; + if (callback) + callback.call(null, rules); }, __proto__: WebInspector.Object.prototype, @@ -128,9 +139,21 @@ WebInspector.CSSParser.prototype = { WebInspector.CSSParser.DataChunk; /** - * @typedef {{selectorText: string, lineNumber: number, columnNumber: number, properties: !Array.}} + * @constructor */ -WebInspector.CSSParser.StyleRule; +WebInspector.CSSParser.StyleRule = function() +{ + /** @type {string} */ + this.selectorText; + /** @type {!WebInspector.CSSParser.Range} */ + this.styleRange; + /** @type {number} */ + this.lineNumber; + /** @type {number} */ + this.columnNumber; + /** @type {!Array.} */ + this.properties; +} /** * @typedef {{atRule: string, lineNumber: number, columnNumber: number}} @@ -165,3 +188,76 @@ WebInspector.CSSParser.Property = function() /** @type {(boolean|undefined)} */ this.disabled; } + +/** + * @constructor + */ +WebInspector.CSSParserService = function() +{ + this._cssParser = null; + this._cssRequests = []; + this._terminated = false; +} + +WebInspector.CSSParserService.prototype = { + /** + * @param {string} text + * @return {!Promise>} + */ + parseCSS: function(text) + { + console.assert(!this._terminated, "Illegal call parseCSS on terminated CSSParserService."); + if (!this._cssParser) + this._cssParser = new WebInspector.CSSParser(); + var request = new WebInspector.CSSParserService.ParseRequest(text); + this._cssRequests.push(request); + this._maybeParseCSS(); + return request.parsedPromise; + }, + + _maybeParseCSS: function() + { + if (this._terminated || this._isParsingCSS || !this._cssRequests.length) + return; + this._isParsingCSS = true; + var request = this._cssRequests.shift(); + this._cssParser.parsePromise(request.text) + .catchException(/** @type {!Array.} */([])) + .then(onCSSParsed.bind(this)); + + /** + * @param {!Array.} rules + * @this {WebInspector.CSSParserService} + */ + function onCSSParsed(rules) + { + request.parsedCallback.call(null, rules); + this._isParsingCSS = false; + this._maybeParseCSS(); + } + }, + + dispose: function() + { + if (this._terminated) + return; + this._terminated = true; + if (this._cssParser) + this._cssParser.dispose(); + for (var request of this._cssRequests) + request.parsedCallback.call(null, /** @type {!Array.} */([])); + this._cssRequests = []; + }, +} + +/** + * @constructor + * @param {string} text + */ +WebInspector.CSSParserService.ParseRequest = function(text) +{ + this.text = text; + /** @type {function(!Array.)} */ + this.parsedCallback; + this.parsedPromise = new Promise(fulfill => this.parsedCallback = fulfill); +} diff --git a/front_end/sdk/CSSStyleModel.js b/front_end/sdk/CSSStyleModel.js index 06759431f7..3707481c45 100644 --- a/front_end/sdk/CSSStyleModel.js +++ b/front_end/sdk/CSSStyleModel.js @@ -70,8 +70,7 @@ WebInspector.CSSStyleModel.Events = { PseudoStateForced: "PseudoStateForced", StyleSheetAdded: "StyleSheetAdded", StyleSheetChanged: "StyleSheetChanged", - StyleSheetRemoved: "StyleSheetRemoved", - ExternalRangeEdit: "ExternalRangeEdit" + StyleSheetRemoved: "StyleSheetRemoved" } WebInspector.CSSStyleModel.MediaTypes = ["all", "braille", "embossed", "handheld", "print", "projection", "screen", "speech", "tty", "tv"]; @@ -79,6 +78,48 @@ WebInspector.CSSStyleModel.MediaTypes = ["all", "braille", "embossed", "handheld WebInspector.CSSStyleModel.PseudoStateMarker = "pseudo-state-marker"; WebInspector.CSSStyleModel.prototype = { + /** + * @param {!Array} styleSheetIds + * @param {!Array} ranges + * @param {!Array} texts + * @param {boolean} majorChange + * @return {!Promise>} + */ + setStyleTexts: function(styleSheetIds, ranges, texts, majorChange) + { + /** + * @param {?Protocol.Error} error + * @param {?Array} stylePayloads + * @return {?Array} + * @this {WebInspector.CSSStyleModel} + */ + function parsePayload(error, stylePayloads) + { + if (error || !stylePayloads || !stylePayloads.length) + return null; + + if (majorChange) + this._domModel.markUndoableState(); + var uniqueIDs = new Set(styleSheetIds); + for (var styleSheetId of uniqueIDs) + this._fireStyleSheetChanged(styleSheetId); + return stylePayloads; + } + + console.assert(styleSheetIds.length === ranges.length && ranges.length === texts.length, "Array lengths must be equal"); + var edits = []; + for (var i = 0; i < styleSheetIds.length; ++i) { + edits.push({ + styleSheetId: styleSheetIds[i], + range: ranges[i].serializeToObject(), + text: texts[i] + }); + } + + return this._agent.setStyleTexts(edits, parsePayload.bind(this)) + .catchException(/** @type {?Array} */(null)); + }, + /** * @return {!Promise.>} */ @@ -946,28 +987,21 @@ WebInspector.CSSStyleDeclaration.prototype = { */ setText: function(text, majorChange) { - if (!this.styleSheetId) - return Promise.resolve(false); - /** - * @param {?Protocol.Error} error - * @param {?CSSAgent.CSSStyle} stylePayload + * @param {?Array} stylePayloads * @return {boolean} * @this {WebInspector.CSSStyleDeclaration} */ - function parsePayload(error, stylePayload) + function onPayload(stylePayloads) { - if (error || !stylePayload) + if (!stylePayloads) return false; - - if (majorChange) - this._cssModel._domModel.markUndoableState(); - this._reinitialize(stylePayload); - this._cssModel._fireStyleSheetChanged(this.styleSheetId); + this._reinitialize(stylePayloads[0]); return true; } - return this._cssModel._agent.setStyleText(this.styleSheetId, this.range.serializeToObject(), text, parsePayload.bind(this)) + return this._cssModel.setStyleTexts([this.styleSheetId], [this.range], [text], majorChange) + .then(onPayload.bind(this)) .catchException(false); }, @@ -1567,7 +1601,7 @@ WebInspector.CSSProperty.prototype = { { if (!insideProperty) { var disabledProperty = tokenType && tokenType.includes("css-comment") && isDisabledProperty(token); - var isPropertyStart = tokenType && (tokenType.includes("css-meta") || tokenType.includes("css-property") || tokenType.includes("css-variable-2")); + var isPropertyStart = tokenType && (tokenType.includes("css-string") || tokenType.includes("css-meta") || tokenType.includes("css-property") || tokenType.includes("css-variable-2")); if (disabledProperty) { result = result.trimRight() + indentation + token; } else if (isPropertyStart) { @@ -1992,13 +2026,16 @@ WebInspector.CSSStyleSheetHeader.prototype = { _trimSourceURL: function(text) { var sourceURLIndex = text.lastIndexOf("/*# sourceURL="); - if (sourceURLIndex === -1) - return text; + if (sourceURLIndex === -1) { + sourceURLIndex = text.lastIndexOf("/*@ sourceURL="); + if (sourceURLIndex === -1) + return text; + } var sourceURLLineIndex = text.lastIndexOf("\n", sourceURLIndex); if (sourceURLLineIndex === -1) return text; var sourceURLLine = text.substr(sourceURLLineIndex + 1).split("\n", 1)[0]; - var sourceURLRegex = /[\040\t]*\/\*# sourceURL=[\040\t]*([^\s]*)[\040\t]*\*\/[\040\t]*$/; + var sourceURLRegex = /[\040\t]*\/\*[#@] sourceURL=[\040\t]*([^\s]*)[\040\t]*\*\/[\040\t]*$/; if (sourceURLLine.search(sourceURLRegex) === -1) return text; return text.substr(0, sourceURLLineIndex) + text.substr(sourceURLLineIndex + sourceURLLine.length + 1); diff --git a/front_end/sdk/DOMModel.js b/front_end/sdk/DOMModel.js index a002f63792..ba8394e8b9 100644 --- a/front_end/sdk/DOMModel.js +++ b/front_end/sdk/DOMModel.js @@ -1084,8 +1084,6 @@ WebInspector.DOMModel = function(target) { this._attributeLoadNodeIds = {}; target.registerDOMDispatcher(new WebInspector.DOMDispatcher(this)); - this._showRulers = false; - this._showExtensionLines = false; this._inspectModeEnabled = false; this._defaultHighlighter = new WebInspector.DefaultDOMNodeHighlighter(this._agent); @@ -1802,16 +1800,6 @@ WebInspector.DOMModel.prototype = { return this._inspectModeEnabled; }, - /** - * @param {boolean} showRulers - * @param {boolean} showExtensionLines - */ - setHighlightSettings: function(showRulers, showExtensionLines) - { - this._showRulers = showRulers; - this._showExtensionLines = showExtensionLines; - }, - /** * @param {string=} mode * @return {!DOMAgent.HighlightConfig} @@ -1819,7 +1807,8 @@ WebInspector.DOMModel.prototype = { _buildHighlightConfig: function(mode) { mode = mode || "all"; - var highlightConfig = { showInfo: mode === "all", showRulers: this._showRulers, showExtensionLines: this._showExtensionLines }; + var showRulers = WebInspector.moduleSetting("showMetricsRulers").get(); + var highlightConfig = { showInfo: mode === "all", showRulers: showRulers, showExtensionLines: showRulers }; if (mode === "all" || mode === "content") highlightConfig.contentColor = WebInspector.Color.PageHighlight.Content.toProtocolRGBA(); diff --git a/front_end/sdk/DebuggerModel.js b/front_end/sdk/DebuggerModel.js index 308efd7a83..ea378c7e27 100644 --- a/front_end/sdk/DebuggerModel.js +++ b/front_end/sdk/DebuggerModel.js @@ -107,6 +107,16 @@ WebInspector.DebuggerModel.BreakReason = { Other: "other" } +/** + * @param {number=} value + * @return {number} + */ +WebInspector.DebuggerModel.fromOneBased = function(value) +{ + // FIXME(webkit:62725): console stack trace line/column numbers are one-based. + return value ? value - 1 : 0; +} + WebInspector.DebuggerModel.prototype = { /** * @return {boolean} @@ -585,10 +595,11 @@ WebInspector.DebuggerModel.prototype = { * @param {boolean} isLiveEdit * @param {string=} sourceMapURL * @param {boolean=} hasSourceURL + * @param {boolean=} deprecatedCommentWasUsed * @param {boolean=} hasSyntaxError * @return {!WebInspector.Script} */ - _parsedScriptSource: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, isContentScript, isInternalScript, isLiveEdit, sourceMapURL, hasSourceURL, hasSyntaxError) + _parsedScriptSource: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, isContentScript, isInternalScript, isLiveEdit, sourceMapURL, hasSourceURL, deprecatedCommentWasUsed, hasSyntaxError) { var script = new WebInspector.Script(this, scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, isContentScript, isInternalScript, isLiveEdit, sourceMapURL, hasSourceURL); this._registerScript(script); @@ -596,6 +607,14 @@ WebInspector.DebuggerModel.prototype = { this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ParsedScriptSource, script); else this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, script); + + if (deprecatedCommentWasUsed) { + var text = WebInspector.UIString("'//@ sourceURL' and '//@ sourceMappingURL' are deprecated, please use '//# sourceURL=' and '//# sourceMappingURL=' instead."); + var msg = new WebInspector.ConsoleMessage(this.target(), WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageLevel.Warning, text, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, scriptId); + var consoleModel = this.target().consoleModel; + if (consoleModel) + consoleModel.addMessage(msg); + } return script; }, @@ -665,6 +684,28 @@ WebInspector.DebuggerModel.prototype = { return script ? this.createRawLocation(script, lineNumber, columnNumber) : null; }, + /** + * @param {!RuntimeAgent.StackTrace} stackTrace + * @return {!Array} + */ + createRawLocationsByStackTrace: function(stackTrace) + { + var frames = []; + while (stackTrace) { + for (var frame of stackTrace.callFrames) + frames.push(frame); + stackTrace = stackTrace.parent; + } + + var rawLocations = []; + for (var frame of frames) { + var rawLocation = this.createRawLocationByScriptId(frame.scriptId, WebInspector.DebuggerModel.fromOneBased(frame.lineNumber), WebInspector.DebuggerModel.fromOneBased(frame.columnNumber)); + if (rawLocation) + rawLocations.push(rawLocation); + } + return rawLocations; + }, + /** * @return {boolean} */ @@ -950,10 +991,11 @@ WebInspector.DebuggerDispatcher.prototype = { * @param {boolean=} isLiveEdit * @param {string=} sourceMapURL * @param {boolean=} hasSourceURL + * @param {boolean=} deprecatedCommentWasUsed */ - scriptParsed: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, isContentScript, isInternalScript, isLiveEdit, sourceMapURL, hasSourceURL) + scriptParsed: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, isContentScript, isInternalScript, isLiveEdit, sourceMapURL, hasSourceURL, deprecatedCommentWasUsed) { - this._debuggerModel._parsedScriptSource(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, !!isContentScript, !!isInternalScript, !!isLiveEdit, sourceMapURL, hasSourceURL, false); + this._debuggerModel._parsedScriptSource(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, !!isContentScript, !!isInternalScript, !!isLiveEdit, sourceMapURL, hasSourceURL, deprecatedCommentWasUsed, false); }, /** @@ -969,10 +1011,11 @@ WebInspector.DebuggerDispatcher.prototype = { * @param {boolean=} isInternalScript * @param {string=} sourceMapURL * @param {boolean=} hasSourceURL + * @param {boolean=} deprecatedCommentWasUsed */ - scriptFailedToParse: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, isContentScript, isInternalScript, sourceMapURL, hasSourceURL) + scriptFailedToParse: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, isContentScript, isInternalScript, sourceMapURL, hasSourceURL, deprecatedCommentWasUsed) { - this._debuggerModel._parsedScriptSource(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, !!isContentScript, !!isInternalScript, false, sourceMapURL, hasSourceURL, true); + this._debuggerModel._parsedScriptSource(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, !!isContentScript, !!isInternalScript, false, sourceMapURL, hasSourceURL, deprecatedCommentWasUsed, true); }, /** diff --git a/front_end/sdk/InspectorBackend.js b/front_end/sdk/InspectorBackend.js index 30381196fb..07f58c2dad 100644 --- a/front_end/sdk/InspectorBackend.js +++ b/front_end/sdk/InspectorBackend.js @@ -274,10 +274,8 @@ InspectorBackendClass.Connection.prototype = { } var messageObject = {}; - var messageId = this.nextMessageId(); messageObject.id = messageId; - messageObject.method = method; if (params) messageObject.params = params; diff --git a/front_end/sdk/ResourceTreeModel.js b/front_end/sdk/ResourceTreeModel.js index acf14dda9e..639981f2e7 100644 --- a/front_end/sdk/ResourceTreeModel.js +++ b/front_end/sdk/ResourceTreeModel.js @@ -115,6 +115,7 @@ WebInspector.ResourceTreeModel.prototype = { { if (error) { this._cachedResourcesProcessed = true; + this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded); return; } diff --git a/front_end/sdk/RuntimeModel.js b/front_end/sdk/RuntimeModel.js index 91933f1230..f5b6c16e44 100644 --- a/front_end/sdk/RuntimeModel.js +++ b/front_end/sdk/RuntimeModel.js @@ -192,11 +192,12 @@ WebInspector.RuntimeModel.prototype = { * @param {number} executionContextId * @param {string=} objectGroup * @param {boolean=} doNotPauseOnExceptionsAndMuteConsole + * @param {boolean=} includeCommandLineAPI * @param {function(?RuntimeAgent.RemoteObject, ?RuntimeAgent.ExceptionDetails=)=} callback */ - runScript: function(scriptId, executionContextId, objectGroup, doNotPauseOnExceptionsAndMuteConsole, callback) + runScript: function(scriptId, executionContextId, objectGroup, doNotPauseOnExceptionsAndMuteConsole, includeCommandLineAPI, callback) { - this._agent.runScript(scriptId, executionContextId, objectGroup, doNotPauseOnExceptionsAndMuteConsole, innerCallback); + this._agent.runScript(scriptId, executionContextId, objectGroup, doNotPauseOnExceptionsAndMuteConsole, includeCommandLineAPI, innerCallback); /** * @param {?Protocol.Error} error diff --git a/front_end/sdk/Script.js b/front_end/sdk/Script.js index 2bb2473090..43a003d47a 100644 --- a/front_end/sdk/Script.js +++ b/front_end/sdk/Script.js @@ -64,7 +64,7 @@ WebInspector.Script.Events = { SourceMapURLAdded: "SourceMapURLAdded" } -WebInspector.Script.sourceURLRegex = /^[\040\t]*\/\/# sourceURL=\s*(\S*?)\s*$/m; +WebInspector.Script.sourceURLRegex = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; /** * @param {string} source @@ -73,8 +73,11 @@ WebInspector.Script.sourceURLRegex = /^[\040\t]*\/\/# sourceURL=\s*(\S*?)\s*$/m; WebInspector.Script._trimSourceURLComment = function(source) { var sourceURLIndex = source.lastIndexOf("//# sourceURL="); - if (sourceURLIndex === -1) - return source; + if (sourceURLIndex === -1) { + sourceURLIndex = source.lastIndexOf("//@ sourceURL="); + if (sourceURLIndex === -1) + return source; + } var sourceURLLineIndex = source.lastIndexOf("\n", sourceURLIndex); if (sourceURLLineIndex === -1) return source; diff --git a/front_end/sdk/TracingModel.js b/front_end/sdk/TracingModel.js index ff3d0ff087..667dc1ae5b 100644 --- a/front_end/sdk/TracingModel.js +++ b/front_end/sdk/TracingModel.js @@ -227,27 +227,28 @@ WebInspector.TracingModel.prototype = { else this._backingStorage.appendString(stringPayload); - if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) { - var timestamp = payload.ts / 1000; - // We do allow records for unrelated threads to arrive out-of-order, - // so there's a chance we're getting records from the past. - if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime)) - this._minimumRecordTime = timestamp; - var endTimeStamp = (payload.ts + (payload.dur || 0)) / 1000; - this._maximumRecordTime = Math.max(this._maximumRecordTime, endTimeStamp); - var event = process._addEvent(payload); - if (!event) - return; - // Build async event when we've got events from all threads & processes, so we can sort them and process in the - // chronological order. However, also add individual async events to the thread flow (above), so we can easily - // display them on the same chart as other events, should we choose so. - if (WebInspector.TracingModel.isAsyncPhase(payload.ph)) - this._asyncEvents.push(event); - event._setBackingStorage(backingStorage); - if (event.hasCategory(WebInspector.TracingModel.DevToolsMetadataEventCategory)) - this._devToolsMetadataEvents.push(event); + var timestamp = payload.ts / 1000; + // We do allow records for unrelated threads to arrive out-of-order, + // so there's a chance we're getting records from the past. + if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime)) + this._minimumRecordTime = timestamp; + var endTimeStamp = (payload.ts + (payload.dur || 0)) / 1000; + this._maximumRecordTime = Math.max(this._maximumRecordTime, endTimeStamp); + var event = process._addEvent(payload); + if (!event) return; - } + // Build async event when we've got events from all threads & processes, so we can sort them and process in the + // chronological order. However, also add individual async events to the thread flow (above), so we can easily + // display them on the same chart as other events, should we choose so. + if (WebInspector.TracingModel.isAsyncPhase(payload.ph)) + this._asyncEvents.push(event); + event._setBackingStorage(backingStorage); + if (event.hasCategory(WebInspector.TracingModel.DevToolsMetadataEventCategory)) + this._devToolsMetadataEvents.push(event); + + if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) + return; + switch (payload.name) { case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex: process._setSortIndex(payload.args["sort_index"]); diff --git a/front_end/sdk/module.json b/front_end/sdk/module.json index 9c64ea4af8..03ce6e379d 100644 --- a/front_end/sdk/module.json +++ b/front_end/sdk/module.json @@ -40,6 +40,12 @@ "settingName": "enableAsyncStackTraces", "settingType": "boolean", "defaultValue": false + }, + { + "type": "setting", + "settingName": "showMetricsRulers", + "settingType": "boolean", + "defaultValue": false } ], "scripts": [ diff --git a/front_end/snippets/ScriptSnippetModel.js b/front_end/snippets/ScriptSnippetModel.js index ed412cc538..9c350e3aca 100644 --- a/front_end/snippets/ScriptSnippetModel.js +++ b/front_end/snippets/ScriptSnippetModel.js @@ -260,7 +260,7 @@ WebInspector.ScriptSnippetModel.prototype = { _runScript: function(scriptId, executionContext, sourceURL) { var target = executionContext.target(); - target.runtimeModel.runScript(scriptId, executionContext.id, "console", false, runCallback.bind(this, target)); + target.runtimeModel.runScript(scriptId, executionContext.id, "console", false, true, runCallback.bind(this, target)); /** * @param {!WebInspector.Target} target diff --git a/front_end/sources/AsyncOperationsSidebarPane.js b/front_end/sources/AsyncOperationsSidebarPane.js index 9b9365febf..2b4c375b81 100644 --- a/front_end/sources/AsyncOperationsSidebarPane.js +++ b/front_end/sources/AsyncOperationsSidebarPane.js @@ -288,10 +288,8 @@ WebInspector.AsyncOperationsSidebarPane.prototype = { var label = createCheckboxLabel(title, operation[this._checkedSymbol]); label.checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, operation.id), false); element.appendChild(label); - var debuggerModel = WebInspector.DebuggerModel.fromTarget(this._target); - var callFrame = WebInspector.DebuggerPresentationUtils.callFrameAnchorFromStackTrace(debuggerModel, operation.stack, this._revealBlackboxedCallFrames); - if (callFrame) - element.createChild("div").appendChild(this._linkifier.linkifyConsoleCallFrame(this._target, callFrame)); + if (operation.stack && operation.stack.callFrames.length) + element.createChild("div").appendChild(this._linkifier.linkifyStackTraceTopFrame(this._target, operation.stack)); element[this._operationIdSymbol] = operation.id; this._operationIdToElement.set(operation.id, element); diff --git a/front_end/sources/CallStackSidebarPane.js b/front_end/sources/CallStackSidebarPane.js index 23ddc267dc..e5652a3f75 100644 --- a/front_end/sources/CallStackSidebarPane.js +++ b/front_end/sources/CallStackSidebarPane.js @@ -169,9 +169,8 @@ WebInspector.CallStackSidebarPane.prototype = { contextMenu.appendItem(WebInspector.UIString.capitalize("Copy ^stack ^trace"), this._copyStackTrace.bind(this)); - var isBlackboxed = WebInspector.blackboxManager.isBlackboxedRawLocation(callFrame._callFrame.location()); - var script = callFrame._callFrame.script; - this.appendBlackboxURLContextMenuItems(contextMenu, script.sourceURL, script.isContentScript(), isBlackboxed); + var uiLocation = WebInspector.debuggerWorkspaceBinding.rawLocationToUILocation(callFrame._callFrame.location()); + this.appendBlackboxURLContextMenuItems(contextMenu, uiLocation.uiSourceCode); contextMenu.show(); }, @@ -193,40 +192,26 @@ WebInspector.CallStackSidebarPane.prototype = { /** * @param {!WebInspector.ContextMenu} contextMenu - * @param {string} url - * @param {boolean} isContentScript - * @param {boolean} isBlackboxed + * @param {!WebInspector.UISourceCode} uiSourceCode */ - appendBlackboxURLContextMenuItems: function(contextMenu, url, isContentScript, isBlackboxed) + appendBlackboxURLContextMenuItems: function(contextMenu, uiSourceCode) { - var canBlackBox = WebInspector.blackboxManager.canBlackboxURL(url); - if (!isBlackboxed && !isContentScript && !canBlackBox) - return; - - if (isBlackboxed) { - contextMenu.appendItem(WebInspector.UIString.capitalize("Stop ^blackboxing"), this._handleContextMenuBlackboxURL.bind(this, url, isContentScript, false)); - } else { - if (canBlackBox) - contextMenu.appendItem(WebInspector.UIString.capitalize("Blackbox ^script"), this._handleContextMenuBlackboxURL.bind(this, url, false, true)); - if (isContentScript) - contextMenu.appendItem(WebInspector.UIString.capitalize("Blackbox ^all ^content ^scripts"), this._handleContextMenuBlackboxURL.bind(this, url, true, true)); + var canBlackbox = WebInspector.blackboxManager.canBlackboxUISourceCode(uiSourceCode); + var isBlackboxed = WebInspector.blackboxManager.isBlackboxedUISourceCode(uiSourceCode); + var isContentScript = uiSourceCode.project().type() === WebInspector.projectTypes.ContentScripts; + + var manager = WebInspector.blackboxManager; + if (canBlackbox) { + if (isBlackboxed) + contextMenu.appendItem(WebInspector.UIString.capitalize("Stop ^blackboxing"), manager.unblackboxUISourceCode.bind(manager, uiSourceCode)); + else + contextMenu.appendItem(WebInspector.UIString.capitalize("Blackbox ^script"), manager.blackboxUISourceCode.bind(manager, uiSourceCode)); } - }, - - /** - * @param {string} url - * @param {boolean} isContentScript - * @param {boolean} blackbox - */ - _handleContextMenuBlackboxURL: function(url, isContentScript, blackbox) - { - if (blackbox) { - if (isContentScript) - WebInspector.moduleSetting("skipContentScripts").set(true); + if (isContentScript) { + if (isBlackboxed) + contextMenu.appendItem(WebInspector.UIString.capitalize("Stop blackboxing ^all ^content ^scripts"), manager.blackboxContentScripts.bind(manager)); else - WebInspector.blackboxManager.blackboxURL(url); - } else { - WebInspector.blackboxManager.unblackbox(url, isContentScript); + contextMenu.appendItem(WebInspector.UIString.capitalize("Blackbox ^all ^content ^scripts"), manager.unblackboxContentScripts.bind(manager)); } }, @@ -425,10 +410,13 @@ WebInspector.CallStackSidebarPane.CallFrame = function(callFrame, asyncCallFrame WebInspector.CallStackSidebarPane.CallFrame.prototype = { /** - * @param {!WebInspector.UILocation} uiLocation + * @param {!WebInspector.LiveLocation} liveLocation */ - _update: function(uiLocation) + _update: function(liveLocation) { + var uiLocation = liveLocation.uiLocation(); + if (!uiLocation) + return; var text = uiLocation.linkText(); this.setSubtitle(text.trimMiddle(30)); this.subtitleElement.title = text; diff --git a/front_end/sources/JavaScriptSourceFrame.js b/front_end/sources/JavaScriptSourceFrame.js index 4841b8bd25..05e319d461 100644 --- a/front_end/sources/JavaScriptSourceFrame.js +++ b/front_end/sources/JavaScriptSourceFrame.js @@ -142,15 +142,16 @@ WebInspector.JavaScriptSourceFrame.prototype = { _showBlackboxInfobarIfNeeded: function() { - if (!this.uiSourceCode().contentType().hasScripts()) + var uiSourceCode = this.uiSourceCode(); + if (!uiSourceCode.contentType().hasScripts()) return; - var projectType = this.uiSourceCode().project().type(); + var projectType = uiSourceCode.project().type(); if (projectType === WebInspector.projectTypes.Snippets) return; - var networkURL = WebInspector.networkMapping.networkURL(this.uiSourceCode()); - var url = projectType === WebInspector.projectTypes.Formatter ? this.uiSourceCode().url() : networkURL; + var networkURL = WebInspector.networkMapping.networkURL(uiSourceCode); + var url = projectType === WebInspector.projectTypes.Formatter ? uiSourceCode.url() : networkURL; var isContentScript = projectType === WebInspector.projectTypes.ContentScripts; - if (!WebInspector.blackboxManager.isBlackboxedUISourceCode(this.uiSourceCode())) { + if (!WebInspector.blackboxManager.isBlackboxedUISourceCode(uiSourceCode)) { this._hideBlackboxInfobar(); return; } @@ -162,6 +163,10 @@ WebInspector.JavaScriptSourceFrame.prototype = { this._blackboxInfobar = infobar; infobar.createDetailsRowMessage(WebInspector.UIString("Debugger will skip stepping through this script, and will not stop on exceptions")); + + var scriptFile = this._scriptFileForTarget.size ? this._scriptFileForTarget.valuesArray()[0] : null; + if (scriptFile && scriptFile.hasSourceMapURL()) + infobar.createDetailsRowMessage(WebInspector.UIString("Source map found, but ignored for blackboxed file.")); infobar.createDetailsRowMessage(); infobar.createDetailsRowMessage(WebInspector.UIString("Possible ways to cancel this behavior are:")); @@ -172,7 +177,9 @@ WebInspector.JavaScriptSourceFrame.prototype = { function unblackbox() { - WebInspector.blackboxManager.unblackbox(url, isContentScript); + WebInspector.blackboxManager.unblackboxUISourceCode(uiSourceCode); + if (projectType === WebInspector.projectTypes.ContentScripts) + WebInspector.blackboxManager.unblackboxContentScripts(); } this._updateInfobars(); @@ -345,7 +352,7 @@ WebInspector.JavaScriptSourceFrame.prototype = { */ function populateSourceMapMembers() { - if (this.uiSourceCode().project().type() === WebInspector.projectTypes.Network && WebInspector.moduleSetting("jsSourceMapsEnabled").get()) { + if (this.uiSourceCode().project().type() === WebInspector.projectTypes.Network && WebInspector.moduleSetting("jsSourceMapsEnabled").get() && !WebInspector.blackboxManager.isBlackboxedUISourceCode(this.uiSourceCode())) { if (this._scriptFileForTarget.size) { var scriptFile = this._scriptFileForTarget.valuesArray()[0]; var addSourceMapURLLabel = WebInspector.UIString.capitalize("Add ^source ^map\u2026"); diff --git a/front_end/sources/SourcesPanel.js b/front_end/sources/SourcesPanel.js index b99dda493a..603e73aa29 100644 --- a/front_end/sources/SourcesPanel.js +++ b/front_end/sources/SourcesPanel.js @@ -242,11 +242,14 @@ WebInspector.SourcesPanel.prototype = { } /** - * @param {!WebInspector.UILocation} uiLocation + * @param {!WebInspector.LiveLocation} liveLocation * @this {WebInspector.SourcesPanel} */ - function didGetUILocation(uiLocation) + function didGetUILocation(liveLocation) { + var uiLocation = liveLocation.uiLocation(); + if (!uiLocation) + return; var breakpoint = WebInspector.breakpointManager.findBreakpointOnLine(uiLocation.uiSourceCode, uiLocation.lineNumber); if (!breakpoint) return; @@ -388,10 +391,13 @@ WebInspector.SourcesPanel.prototype = { }, /** - * @param {!WebInspector.UILocation} uiLocation + * @param {!WebInspector.LiveLocation} liveLocation */ - _executionLineChanged: function(uiLocation) + _executionLineChanged: function(liveLocation) { + var uiLocation = liveLocation.uiLocation(); + if (!uiLocation) + return; this._sourcesView.clearCurrentExecutionLine(); this._sourcesView.setExecutionLocation(uiLocation); if (window.performance.now() - this._lastModificationTime < WebInspector.SourcesPanel._lastModificationTimeout) @@ -925,12 +931,8 @@ WebInspector.SourcesPanel.prototype = { contextMenu.appendItem(WebInspector.UIString.capitalize("Continue to ^here"), this._continueToLocation.bind(this, uiLocation)); } - if (contentType.hasScripts() && projectType !== WebInspector.projectTypes.Snippets) { - var networkURL = this._networkMapping.networkURL(uiSourceCode); - var url = projectType === WebInspector.projectTypes.Formatter ? uiSourceCode.url() : networkURL; - var isBlackboxed = WebInspector.blackboxManager.isBlackboxedUISourceCode(uiSourceCode); - this.sidebarPanes.callstack.appendBlackboxURLContextMenuItems(contextMenu, url, projectType === WebInspector.projectTypes.ContentScripts, isBlackboxed); - } + if (contentType.hasScripts() && projectType !== WebInspector.projectTypes.Snippets) + this.sidebarPanes.callstack.appendBlackboxURLContextMenuItems(contextMenu, uiSourceCode); }, /** diff --git a/front_end/timeline/TimelineFlameChart.js b/front_end/timeline/TimelineFlameChart.js index 34e507b9e7..05b2e7df5b 100644 --- a/front_end/timeline/TimelineFlameChart.js +++ b/front_end/timeline/TimelineFlameChart.js @@ -76,6 +76,15 @@ WebInspector.TimelineFlameChartDataProviderBase.prototype = { return 4; }, + /** + * @return {number} + * @override + */ + groupSeparatorHeight: function() + { + return 3; + }, + /** * @override * @param {number} entryIndex @@ -358,7 +367,7 @@ WebInspector.TimelineFlameChartDataProvider.prototype = { if (this._timelineData) return this._timelineData; - this._timelineData = new WebInspector.FlameChart.TimelineData([], [], []); + this._timelineData = new WebInspector.FlameChart.TimelineData([], [], [], []); this._flowEventIndexById = {}; this._minimumBoundary = this._model.minimumRecordTime(); @@ -436,7 +445,7 @@ WebInspector.TimelineFlameChartDataProvider.prototype = { e._blackboxRoot = true; } if (headerName) { - this._appendHeaderRecord(headerName); + this._appendHeader(headerName); headerName = null; } @@ -502,7 +511,7 @@ WebInspector.TimelineFlameChartDataProvider.prototype = { if (!this._isVisible(asyncEvent)) continue; if (!groupHeaderAppended) { - this._appendHeaderRecord(header); + this._appendHeader(header); groupHeaderAppended = true; } var startTime = asyncEvent.startTime; @@ -709,6 +718,17 @@ WebInspector.TimelineFlameChartDataProvider.prototype = { type === WebInspector.TimelineFlameChartEntryType.Event && !!/** @type {!WebInspector.TracingModel.Event} */ (this._entryData[entryIndex]).warning; }, + /** + * @param {string} title + */ + _appendHeader: function(title) + { + if (Runtime.experiments.isEnabled("timelineCollapsible")) + this._timelineData.groups.push({startLevel: this._currentLevel, name: title, expanded: true}); + else + this._appendHeaderRecord(title) + }, + /** * @param {string} title */ @@ -818,7 +838,7 @@ WebInspector.TimelineFlameChartDataProvider.prototype = { }, /** - * @param {!WebInspector.Segment} segment + * @param {!Segment} segment */ _appendSegment: function(segment) { @@ -892,7 +912,7 @@ WebInspector.TimelineFlameChartNetworkDataProvider.prototype = { return this._timelineData; /** @type {!Array} */ this._requests = []; - this._timelineData = new WebInspector.FlameChart.TimelineData([], [], []); + this._timelineData = new WebInspector.FlameChart.TimelineData([], [], [], []); this._appendTimelineData(this._model.mainThreadEvents()); return this._timelineData; }, @@ -1112,7 +1132,8 @@ WebInspector.TimelineFlameChartNetworkDataProvider.prototype = { this._timelineData = new WebInspector.FlameChart.TimelineData( this._timelineData.entryLevels, this._timelineData.entryTotalTimes, - this._timelineData.entryStartTimes); + this._timelineData.entryStartTimes, + null); this._currentLevel = index; }, @@ -1231,10 +1252,10 @@ WebInspector.TimelineFlameChartView = function(delegate, timelineModel, frameMod this._splitWidget = new WebInspector.SplitWidget(false, false, "timelineFlamechartMainView", 150); this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(this._model, frameModel, irModel); - this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true); + this._mainView = new WebInspector.FlameChart(this._dataProvider, this); this._networkDataProvider = new WebInspector.TimelineFlameChartNetworkDataProvider(this._model); - this._networkView = new WebInspector.FlameChart(this._networkDataProvider, this, true); + this._networkView = new WebInspector.FlameChart(this._networkDataProvider, this); if (Runtime.experiments.isEnabled("networkRequestsOnTimeline")) { this._splitWidget.setMainWidget(this._mainView); diff --git a/front_end/timeline/TimelineIRModel.js b/front_end/timeline/TimelineIRModel.js index 03483d9ef0..ffaf571796 100644 --- a/front_end/timeline/TimelineIRModel.js +++ b/front_end/timeline/TimelineIRModel.js @@ -82,7 +82,7 @@ WebInspector.TimelineIRModel.prototype = { var animations = asyncEventsByGroup.get(groups.animation); if (animations) this._processAnimations(animations); - var range = new WebInspector.SegmentedRange(); + var range = new SegmentedRange(); range.appendRange(this._drags); // Drags take lower precedence than animation, as we can't detect them reliably. range.appendRange(this._cssAnimations); range.appendRange(this._scrolls); @@ -129,7 +129,7 @@ WebInspector.TimelineIRModel.prototype = { // FIXME: also process renderer fling events. if (!flingStart) break; - this._scrolls.append(new WebInspector.Segment(flingStart.startTime, event.endTime, phases.Fling)); + this._scrolls.append(new Segment(flingStart.startTime, event.endTime, phases.Fling)); flingStart = null; break; @@ -168,7 +168,7 @@ WebInspector.TimelineIRModel.prototype = { this._drags.append(this._segmentForEvent(event, phases.Drag)); } else if (touchStart) { firstTouchMove = event; - this._responses.append(new WebInspector.Segment(touchStart.startTime, event.endTime, phases.Response)); + this._responses.append(new Segment(touchStart.startTime, event.endTime, phases.Response)); } break; @@ -199,7 +199,7 @@ WebInspector.TimelineIRModel.prototype = { case eventTypes.MouseWheel: // Do not consider first MouseWheel as trace viewer's implementation does -- in case of MouseWheel it's not really special. if (mouseWheel && canMerge(thresholdsMs.mouse, mouseWheel, event)) - this._scrolls.append(new WebInspector.Segment(mouseWheel.endTime, event.startTime, phases.Scroll)); + this._scrolls.append(new Segment(mouseWheel.endTime, event.startTime, phases.Scroll)); this._scrolls.append(this._segmentForEvent(event, phases.Scroll)); mouseWheel = event; break; @@ -230,15 +230,15 @@ WebInspector.TimelineIRModel.prototype = { /** * @param {!WebInspector.TracingModel.AsyncEvent} event * @param {!WebInspector.TimelineIRModel.Phases} phase - * @return {!WebInspector.Segment} + * @return {!Segment} */ _segmentForEvent: function(event, phase) { - return new WebInspector.Segment(event.startTime, event.endTime, phase); + return new Segment(event.startTime, event.endTime, phase); }, /** - * @return {!Array} + * @return {!Array} */ interactionRecords: function() { @@ -250,15 +250,15 @@ WebInspector.TimelineIRModel.prototype = { var thresholdsMs = WebInspector.TimelineIRModel._mergeThresholdsMs; this._segments = []; - this._drags = new WebInspector.SegmentedRange(merge.bind(null, thresholdsMs.mouse)); - this._cssAnimations = new WebInspector.SegmentedRange(merge.bind(null, thresholdsMs.animation)); - this._responses = new WebInspector.SegmentedRange(merge.bind(null, 0)); - this._scrolls = new WebInspector.SegmentedRange(merge.bind(null, thresholdsMs.animation)); + this._drags = new SegmentedRange(merge.bind(null, thresholdsMs.mouse)); + this._cssAnimations = new SegmentedRange(merge.bind(null, thresholdsMs.animation)); + this._responses = new SegmentedRange(merge.bind(null, 0)); + this._scrolls = new SegmentedRange(merge.bind(null, thresholdsMs.animation)); /** * @param {number} threshold - * @param {!WebInspector.Segment} first - * @param {!WebInspector.Segment} second + * @param {!Segment} first + * @param {!Segment} second */ function merge(threshold, first, second) { @@ -283,111 +283,3 @@ WebInspector.TimelineIRModel.prototype = { } }; -/** - * @constructor - * @param {(function(!WebInspector.Segment, !WebInspector.Segment): ?WebInspector.Segment)=} mergeCallback - */ -WebInspector.SegmentedRange = function(mergeCallback) -{ - /** @type {!Array} */ - this._segments = []; - this._mergeCallback = mergeCallback; -} - -/** - * @constructor - * @param {number} begin - * @param {number} end - * @param {*} data - */ -WebInspector.Segment = function(begin, end, data) -{ - if (begin > end) - console.assert(false, "Invalid segment"); - this.begin = begin; - this.end = end; - this.data = data; -} - -WebInspector.Segment.prototype = { - /** - * @param {!WebInspector.Segment} that - * @return {boolean} - */ - intersects: function(that) - { - return this.begin < that.end && that.begin < this.end; - } -}; - -WebInspector.SegmentedRange.prototype = { - /** - * @param {!WebInspector.Segment} newSegment - */ - append: function(newSegment) - { - // 1. Find the proper insertion point for new segment - var startIndex = this._segments.lowerBound(newSegment, (a, b) => a.begin - b.begin); - var endIndex = startIndex; - var merged = null; - if (startIndex > 0) { - // 2. Try mering the preceding segment - var precedingSegment = this._segments[startIndex - 1]; - merged = this._tryMerge(precedingSegment, newSegment); - if (merged) { - --startIndex; - newSegment = merged; - } else if (this._segments[startIndex - 1].end >= newSegment.begin) { - // 2a. If merge failed and segments overlap, adjust preceding segment. - // If an old segment entirely contains new one, split it in two. - if (newSegment.end < precedingSegment.end) - this._segments.splice(startIndex, 0, new WebInspector.Segment(newSegment.end, precedingSegment.end, precedingSegment.data)); - precedingSegment.end = newSegment.begin; - } - } - // 3. Consume all segments that are entirely covered by the new one. - while (endIndex < this._segments.length && this._segments[endIndex].end <= newSegment.end) - ++endIndex; - // 4. Merge or adjust the succeeding segment if it overlaps. - if (endIndex < this._segments.length) { - merged = this._tryMerge(newSegment, this._segments[endIndex]); - if (merged) { - endIndex++; - newSegment = merged; - } else if (newSegment.intersects(this._segments[endIndex])) - this._segments[endIndex].begin = newSegment.end; - } - this._segments.splice(startIndex, endIndex - startIndex, newSegment); - }, - - /** - * @param {!WebInspector.SegmentedRange} that - */ - appendRange: function(that) - { - that.segments().forEach(segment => this.append(segment)); - }, - - /** - * @return {!Array} - */ - segments: function() - { - return this._segments; - }, - - /** - * @param {!WebInspector.Segment} first - * @param {!WebInspector.Segment} second - * @return {?WebInspector.Segment} - */ - _tryMerge: function(first, second) - { - var merged = this._mergeCallback && this._mergeCallback(first, second); - if (!merged) - return null; - merged.begin = first.begin; - merged.end = Math.max(first.end, second.end); - return merged; - } -} diff --git a/front_end/timeline/TimelineJSProfile.js b/front_end/timeline/TimelineJSProfile.js index 3d74497f41..ea1e23a312 100644 --- a/front_end/timeline/TimelineJSProfile.js +++ b/front_end/timeline/TimelineJSProfile.js @@ -86,29 +86,9 @@ WebInspector.TimelineJSProfileProcessor.generateJSFrameEvents = function(events) var jsFrameEvents = []; var jsFramesStack = []; var lockedJsStackDepth = []; - var currentSamplingIntervalMs = 0.1; - var lastStackSampleTime = 0; var ordinal = 0; var filterNativeFunctions = !WebInspector.moduleSetting("showNativeFunctionsInJSProfile").get(); - /** - * @param {!WebInspector.TracingModel.Event} e - */ - function updateSamplingInterval(e) - { - if (e.name !== WebInspector.TimelineModel.RecordType.JSSample) - return; - var time = e.startTime; - var interval = time - lastStackSampleTime; - lastStackSampleTime = time; - // Do not take into account intervals longer than 10ms. - if (!interval || interval > 10) - return; - // Use exponential moving average with a smoothing factor of 0.1 - var alpha = 0.1; - currentSamplingIntervalMs += alpha * (interval - currentSamplingIntervalMs); - } - /** * @param {!WebInspector.TracingModel.Event} e */ @@ -127,7 +107,6 @@ WebInspector.TimelineJSProfileProcessor.generateJSFrameEvents = function(events) function onInstantEvent(e, parent) { e.ordinal = ++ordinal; - updateSamplingInterval(e); if (parent && isJSInvocationEvent(parent)) extractStackTrace(e); } @@ -157,11 +136,8 @@ WebInspector.TimelineJSProfileProcessor.generateJSFrameEvents = function(events) console.error("Trying to truncate higher than the current stack size at " + time); depth = jsFramesStack.length; } - var minFrameDurationMs = currentSamplingIntervalMs / 2; - for (var k = 0; k < depth; ++k) + for (var k = 0; k < jsFramesStack.length; ++k) jsFramesStack[k].setEndTime(time); - for (var k = depth; k < jsFramesStack.length; ++k) - jsFramesStack[k].setEndTime(Math.min(eventEndTime(jsFramesStack[k]) + minFrameDurationMs, time)); jsFramesStack.length = depth; } diff --git a/front_end/timeline/TimelineLoader.js b/front_end/timeline/TimelineLoader.js new file mode 100644 index 0000000000..e937c9a366 --- /dev/null +++ b/front_end/timeline/TimelineLoader.js @@ -0,0 +1,300 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + * @implements {WebInspector.OutputStream} + * @param {!WebInspector.TimelineModel} model + * @param {!WebInspector.Progress} progress + * @param {function()=} canceledCallback + */ +WebInspector.TimelineLoader = function(model, progress, canceledCallback) +{ + this._model = model; + + this._canceledCallback = canceledCallback; + this._progress = progress; + this._progress.setTitle(WebInspector.UIString("Loading")); + this._progress.setTotalWork(WebInspector.TimelineLoader._totalProgress); // Unknown, will loop the values. + + this._state = WebInspector.TimelineLoader.State.Initial; + this._buffer = ""; + this._firstChunk = true; + this._wasCanceledOnce = false; + + this._loadedBytes = 0; + this._jsonTokenizer = new WebInspector.TextUtils.BalancedJSONTokenizer(this._writeBalancedJSON.bind(this), true); +} + +/** + * @param {!WebInspector.TimelineModel} model + * @param {!File} file + * @param {!WebInspector.Progress} progress + */ +WebInspector.TimelineLoader.loadFromFile = function(model, file, progress) +{ + var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(model, progress); + var fileReader = WebInspector.TimelineLoader._createFileReader(file, delegate); + var loader = new WebInspector.TimelineLoader(model, new WebInspector.ProgressProxy(null), fileReader.cancel.bind(fileReader)); + fileReader.start(loader); +} + +/** + * @param {!WebInspector.TimelineModel} model + * @param {string} url + * @param {!WebInspector.Progress} progress + */ +WebInspector.TimelineLoader.loadFromURL = function(model, url, progress) +{ + var stream = new WebInspector.TimelineLoader(model, progress); + WebInspector.ResourceLoader.loadAsStream(url, null, stream); +} + +/** + * @param {!File} file + * @param {!WebInspector.OutputStreamDelegate} delegate + * @return {!WebInspector.ChunkedReader} + */ +WebInspector.TimelineLoader._createFileReader = function(file, delegate) +{ + return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.TransferChunkLengthBytes, delegate); +} + + +WebInspector.TimelineLoader._totalProgress = 100000; + +WebInspector.TimelineLoader.State = { + Initial: "Initial", + LookingForEvents: "LookingForEvents", + ReadingEvents: "ReadingEvents" +} + +WebInspector.TimelineLoader.prototype = { + /** + * @override + * @param {string} chunk + */ + write: function(chunk) + { + this._loadedBytes += chunk.length; + if (this._progress.isCanceled() && !this._wasCanceledOnce) { + this._wasCanceled = true; + this._reportErrorAndCancelLoading(); + return; + } + this._progress.setWorked(this._loadedBytes % WebInspector.TimelineLoader._totalProgress, + WebInspector.UIString("Loaded %s", Number.bytesToString(this._loadedBytes))); + if (this._state === WebInspector.TimelineLoader.State.Initial) { + if (chunk[0] === "{") + this._state = WebInspector.TimelineLoader.State.LookingForEvents; + else if (chunk[0] === "[") + this._state = WebInspector.TimelineLoader.State.ReadingEvents; + else { + this._reportErrorAndCancelLoading(WebInspector.UIString("Malformed timeline data: Unknown JSON format")); + return; + } + } + + if (this._state === WebInspector.TimelineLoader.State.LookingForEvents) { + var objectName = "\"traceEvents\":"; + var startPos = this._buffer.length - objectName.length; + this._buffer += chunk; + var pos = this._buffer.indexOf(objectName, startPos); + if (pos === -1) + return; + chunk = this._buffer.slice(pos + objectName.length) + this._state = WebInspector.TimelineLoader.State.ReadingEvents; + } + + this._jsonTokenizer.write(chunk); + }, + + /** + * @param {string} data + */ + _writeBalancedJSON: function(data) + { + var json = data + "]"; + + if (this._firstChunk) { + this._model.startCollectingTraceEvents(true); + } else { + var commaIndex = json.indexOf(","); + if (commaIndex !== -1) + json = json.slice(commaIndex + 1); + json = "[" + json; + } + + var items; + try { + items = /** @type {!Array.} */ (JSON.parse(json)); + } catch (e) { + this._reportErrorAndCancelLoading(WebInspector.UIString("Malformed timeline data: %s", e.toString())); + return; + } + + if (this._firstChunk) { + this._firstChunk = false; + if (this._looksLikeAppVersion(items[0])) { + this._reportErrorAndCancelLoading(WebInspector.UIString("Legacy Timeline format is not supported.")); + return; + } + } + + try { + this._model.traceEventsCollected(items); + } catch(e) { + this._reportErrorAndCancelLoading(WebInspector.UIString("Malformed timeline data: %s", e.toString())); + return; + } + }, + + /** + * @param {string=} message + */ + _reportErrorAndCancelLoading: function(message) + { + if (message) + WebInspector.console.error(message); + this._model.tracingComplete(); + this._model.reset(); + if (this._canceledCallback) + this._canceledCallback(); + this._progress.done(); + }, + + /** + * @param {*} item + * @return {boolean} + */ + _looksLikeAppVersion: function(item) + { + return typeof item === "string" && item.indexOf("Chrome") !== -1; + }, + + /** + * @override + */ + close: function() + { + this._model._loadedFromFile = true; + this._model.tracingComplete(); + if (this._progress) + this._progress.done(); + } +} + +/** + * @constructor + * @implements {WebInspector.OutputStreamDelegate} + * @param {!WebInspector.TimelineModel} model + * @param {!WebInspector.Progress} progress + */ +WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress) +{ + this._model = model; + this._progress = progress; +} + +WebInspector.TimelineModelLoadFromFileDelegate.prototype = { + /** + * @override + */ + onTransferStarted: function() + { + this._progress.setTitle(WebInspector.UIString("Loading\u2026")); + }, + + /** + * @override + * @param {!WebInspector.ChunkedReader} reader + */ + onChunkTransferred: function(reader) + { + if (this._progress.isCanceled()) { + reader.cancel(); + this._progress.done(); + this._model.reset(); + return; + } + + var totalSize = reader.fileSize(); + if (totalSize) { + this._progress.setTotalWork(totalSize); + this._progress.setWorked(reader.loadedSize()); + } + }, + + /** + * @override + */ + onTransferFinished: function() + { + this._progress.done(); + }, + + /** + * @override + * @param {!WebInspector.ChunkedReader} reader + * @param {!Event} event + */ + onError: function(reader, event) + { + this._progress.done(); + this._model.reset(); + switch (event.target.error.code) { + case FileError.NOT_FOUND_ERR: + WebInspector.console.error(WebInspector.UIString("File \"%s\" not found.", reader.fileName())); + break; + case FileError.NOT_READABLE_ERR: + WebInspector.console.error(WebInspector.UIString("File \"%s\" is not readable", reader.fileName())); + break; + case FileError.ABORT_ERR: + break; + default: + WebInspector.console.error(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName())); + } + } +} + +/** + * @constructor + * @param {!WebInspector.OutputStream} stream + * @implements {WebInspector.OutputStreamDelegate} + */ +WebInspector.TracingTimelineSaver = function(stream) +{ + this._stream = stream; +} + +WebInspector.TracingTimelineSaver.prototype = { + /** + * @override + */ + onTransferStarted: function() + { + this._stream.write("["); + }, + + /** + * @override + */ + onTransferFinished: function() + { + this._stream.write("]"); + }, + + /** + * @override + * @param {!WebInspector.ChunkedReader} reader + */ + onChunkTransferred: function(reader) { }, + + /** + * @override + * @param {!WebInspector.ChunkedReader} reader + * @param {!Event} event + */ + onError: function(reader, event) { } +} diff --git a/front_end/timeline/TimelineModel.js b/front_end/timeline/TimelineModel.js index 8786a5129f..21905ea098 100644 --- a/front_end/timeline/TimelineModel.js +++ b/front_end/timeline/TimelineModel.js @@ -551,7 +551,7 @@ WebInspector.TimelineModel.prototype = { */ setEventsForTest: function(events) { - this._startCollectingTraceEvents(false); + this.startCollectingTraceEvents(false); this._tracingModel.addEvents(events); this.tracingComplete(); }, @@ -658,7 +658,7 @@ WebInspector.TimelineModel.prototype = { /** * @param {boolean} fromFile */ - _startCollectingTraceEvents: function(fromFile) + startCollectingTraceEvents: function(fromFile) { this._tracingModel.reset(); this.reset(); @@ -670,7 +670,7 @@ WebInspector.TimelineModel.prototype = { */ tracingStarted: function() { - this._startCollectingTraceEvents(false); + this.startCollectingTraceEvents(false); }, /** @@ -1348,33 +1348,6 @@ WebInspector.TimelineModel.prototype = { } }, - /** - * @param {!Blob} file - * @param {!WebInspector.Progress} progress - */ - loadFromFile: function(file, progress) - { - var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress); - var fileReader = this._createFileReader(file, delegate); - var loader = new WebInspector.TracingModelLoader(this, new WebInspector.ProgressProxy(null), fileReader.cancel.bind(fileReader)); - fileReader.start(loader); - }, - - /** - * @param {string} url - * @param {!WebInspector.Progress} progress - */ - loadFromURL: function(url, progress) - { - var stream = new WebInspector.TracingModelLoader(this, progress); - WebInspector.ResourceLoader.loadAsStream(url, null, stream); - }, - - _createFileReader: function(file, delegate) - { - return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.TransferChunkLengthBytes, delegate); - }, - reset: function() { this._virtualThreads = []; @@ -1625,79 +1598,6 @@ WebInspector.TimelineVisibleEventsFilter.prototype = { __proto__: WebInspector.TimelineModel.Filter.prototype } -/** - * @constructor - * @implements {WebInspector.OutputStreamDelegate} - * @param {!WebInspector.TimelineModel} model - * @param {!WebInspector.Progress} progress - */ -WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress) -{ - this._model = model; - this._progress = progress; -} - -WebInspector.TimelineModelLoadFromFileDelegate.prototype = { - /** - * @override - */ - onTransferStarted: function() - { - this._progress.setTitle(WebInspector.UIString("Loading\u2026")); - }, - - /** - * @override - * @param {!WebInspector.ChunkedReader} reader - */ - onChunkTransferred: function(reader) - { - if (this._progress.isCanceled()) { - reader.cancel(); - this._progress.done(); - this._model.reset(); - return; - } - - var totalSize = reader.fileSize(); - if (totalSize) { - this._progress.setTotalWork(totalSize); - this._progress.setWorked(reader.loadedSize()); - } - }, - - /** - * @override - */ - onTransferFinished: function() - { - this._progress.done(); - }, - - /** - * @override - * @param {!WebInspector.ChunkedReader} reader - * @param {!Event} event - */ - onError: function(reader, event) - { - this._progress.done(); - this._model.reset(); - switch (event.target.error.code) { - case FileError.NOT_FOUND_ERR: - WebInspector.console.error(WebInspector.UIString("File \"%s\" not found.", reader.fileName())); - break; - case FileError.NOT_READABLE_ERR: - WebInspector.console.error(WebInspector.UIString("File \"%s\" is not readable", reader.fileName())); - break; - case FileError.ABORT_ERR: - break; - default: - WebInspector.console.error(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName())); - } - } -} - /** * @constructor * @extends {WebInspector.TimelineModel.Filter} @@ -1746,191 +1646,6 @@ WebInspector.ExcludeTopLevelFilter.prototype = { __proto__: WebInspector.TimelineModel.Filter.prototype } -/** - * @constructor - * @implements {WebInspector.OutputStream} - * @param {!WebInspector.TimelineModel} model - * @param {!WebInspector.Progress} progress - * @param {function()=} canceledCallback - */ -WebInspector.TracingModelLoader = function(model, progress, canceledCallback) -{ - this._model = model; - - this._canceledCallback = canceledCallback; - this._progress = progress; - this._progress.setTitle(WebInspector.UIString("Loading")); - this._progress.setTotalWork(WebInspector.TracingModelLoader._totalProgress); // Unknown, will loop the values. - - this._state = WebInspector.TracingModelLoader.State.Initial; - this._buffer = ""; - this._firstChunk = true; - this._wasCanceledOnce = false; - - this._loadedBytes = 0; - this._jsonTokenizer = new WebInspector.TextUtils.BalancedJSONTokenizer(this._writeBalancedJSON.bind(this), true); -} - -WebInspector.TracingModelLoader._totalProgress = 100000; - -WebInspector.TracingModelLoader.State = { - Initial: "Initial", - LookingForEvents: "LookingForEvents", - ReadingEvents: "ReadingEvents" -} - -WebInspector.TracingModelLoader.prototype = { - /** - * @override - * @param {string} chunk - */ - write: function(chunk) - { - this._loadedBytes += chunk.length; - if (this._progress.isCanceled() && !this._wasCanceledOnce) { - this._wasCanceled = true; - this._reportErrorAndCancelLoading(); - return; - } - this._progress.setWorked(this._loadedBytes % WebInspector.TracingModelLoader._totalProgress, - WebInspector.UIString("Loaded %s", Number.bytesToString(this._loadedBytes))); - if (this._state === WebInspector.TracingModelLoader.State.Initial) { - if (chunk[0] === "{") - this._state = WebInspector.TracingModelLoader.State.LookingForEvents; - else if (chunk[0] === "[") - this._state = WebInspector.TracingModelLoader.State.ReadingEvents; - else { - this._reportErrorAndCancelLoading(WebInspector.UIString("Malformed timeline data: Unknown JSON format")); - return; - } - } - - if (this._state === WebInspector.TracingModelLoader.State.LookingForEvents) { - var objectName = "\"traceEvents\":"; - var startPos = this._buffer.length - objectName.length; - this._buffer += chunk; - var pos = this._buffer.indexOf(objectName, startPos); - if (pos === -1) - return; - chunk = this._buffer.slice(pos + objectName.length) - this._state = WebInspector.TracingModelLoader.State.ReadingEvents; - } - - this._jsonTokenizer.write(chunk); - }, - - /** - * @param {string} data - */ - _writeBalancedJSON: function(data) - { - var json = data + "]"; - - if (this._firstChunk) { - this._model._startCollectingTraceEvents(true); - } else { - var commaIndex = json.indexOf(","); - if (commaIndex !== -1) - json = json.slice(commaIndex + 1); - json = "[" + json; - } - - var items; - try { - items = /** @type {!Array.} */ (JSON.parse(json)); - } catch (e) { - this._reportErrorAndCancelLoading(WebInspector.UIString("Malformed timeline data: %s", e.toString())); - return; - } - - if (this._firstChunk) { - this._firstChunk = false; - if (this._looksLikeAppVersion(items[0])) { - this._reportErrorAndCancelLoading(WebInspector.UIString("Legacy Timeline format is not supported.")); - return; - } - } - - try { - this._model._tracingModel.addEvents(items); - } catch(e) { - this._reportErrorAndCancelLoading(WebInspector.UIString("Malformed timeline data: %s", e.toString())); - return; - } - }, - - /** - * @param {string=} message - */ - _reportErrorAndCancelLoading: function(message) - { - if (message) - WebInspector.console.error(message); - this._model.tracingComplete(); - this._model.reset(); - if (this._canceledCallback) - this._canceledCallback(); - this._progress.done(); - }, - - _looksLikeAppVersion: function(item) - { - return typeof item === "string" && item.indexOf("Chrome") !== -1; - }, - - /** - * @override - */ - close: function() - { - this._model._loadedFromFile = true; - this._model.tracingComplete(); - if (this._progress) - this._progress.done(); - } -} - -/** - * @constructor - * @param {!WebInspector.OutputStream} stream - * @implements {WebInspector.OutputStreamDelegate} - */ -WebInspector.TracingTimelineSaver = function(stream) -{ - this._stream = stream; -} - -WebInspector.TracingTimelineSaver.prototype = { - /** - * @override - */ - onTransferStarted: function() - { - this._stream.write("["); - }, - - /** - * @override - */ - onTransferFinished: function() - { - this._stream.write("]"); - }, - - /** - * @override - * @param {!WebInspector.ChunkedReader} reader - */ - onChunkTransferred: function(reader) { }, - - /** - * @override - * @param {!WebInspector.ChunkedReader} reader - * @param {!Event} event - */ - onError: function(reader, event) { } -} - /** * @constructor * @param {!WebInspector.TracingModel.Event} event diff --git a/front_end/timeline/TimelinePanel.js b/front_end/timeline/TimelinePanel.js index 8a67949084..dab326240c 100644 --- a/front_end/timeline/TimelinePanel.js +++ b/front_end/timeline/TimelinePanel.js @@ -536,7 +536,7 @@ WebInspector.TimelinePanel.prototype = { { if (this._state !== WebInspector.TimelinePanel.State.Idle) return; - this._model.loadFromFile(file, this._prepareToLoadTimeline()); + WebInspector.TimelineLoader.loadFromFile(this._model, file, this._prepareToLoadTimeline()); this._createFileSelector(); }, @@ -547,7 +547,7 @@ WebInspector.TimelinePanel.prototype = { { if (this._state !== WebInspector.TimelinePanel.State.Idle) return; - this._model.loadFromURL(url, this._prepareToLoadTimeline()); + WebInspector.TimelineLoader.loadFromURL(this._model, url, this._prepareToLoadTimeline()); }, _refreshViews: function() diff --git a/front_end/timeline/TimelineProfileTree.js b/front_end/timeline/TimelineProfileTree.js new file mode 100644 index 0000000000..6c59ee6270 --- /dev/null +++ b/front_end/timeline/TimelineProfileTree.js @@ -0,0 +1,180 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +WebInspector.TimelineProfileTree = { }; + +/** + * @constructor + */ +WebInspector.TimelineProfileTree.Node = function() +{ + /** @type {number} */ + this.totalTime; + /** @type {number} */ + this.selfTime; + /** @type {string} */ + this.name; + /** @type {string} */ + this.color; + /** @type {string} */ + this.id; + /** @type {!WebInspector.TracingModel.Event} */ + this.event; + /** @type {?Map} */ + this.children; + /** @type {?WebInspector.TimelineProfileTree.Node} */ + this.parent; +} + +/** + * @param {!Array} events + * @param {!Array} filters + * @param {number} startTime + * @param {number} endTime + * @param {function(!WebInspector.TracingModel.Event):(string|symbol)=} eventIdCallback + * @return {!WebInspector.TimelineProfileTree.Node} + */ +WebInspector.TimelineProfileTree.buildTopDown = function(events, filters, startTime, endTime, eventIdCallback) +{ + // Temporarily deposit a big enough value that exceeds the max recording time. + var /** @const */ initialTime = 1e7; + var root = new WebInspector.TimelineProfileTree.Node(); + root.totalTime = initialTime; + root.selfTime = initialTime; + root.name = WebInspector.UIString("Top-Down Chart"); + root.children = /** @type {!Map} */ (new Map()); + var parent = root; + + /** + * @param {!WebInspector.TracingModel.Event} e + */ + function onStartEvent(e) + { + if (!WebInspector.TimelineModel.isVisible(filters, e)) + return; + var time = e.endTime ? Math.min(endTime, e.endTime) - Math.max(startTime, e.startTime) : 0; + var id = eventIdCallback ? eventIdCallback(e) : Symbol("uniqueEventId"); + if (!parent.children) + parent.children = /** @type {!Map} */ (new Map()); + var node = parent.children.get(id); + if (node) { + node.selfTime += time; + node.totalTime += time; + } else { + node = new WebInspector.TimelineProfileTree.Node(); + node.totalTime = time; + node.selfTime = time; + node.parent = parent; + node.id = id; + node.event = e; + parent.children.set(id, node); + } + parent.selfTime -= time; + if (parent.selfTime < 0) { + console.log("Error: Negative self of " + parent.selfTime, e); + parent.selfTime = 0; + } + if (e.endTime) + parent = node; + } + + /** + * @param {!WebInspector.TracingModel.Event} e + */ + function onEndEvent(e) + { + if (!WebInspector.TimelineModel.isVisible(filters, e)) + return; + parent = parent.parent; + } + + var instantEventCallback = eventIdCallback ? undefined : onStartEvent; // Ignore instant events when aggregating. + WebInspector.TimelineModel.forEachEvent(events, onStartEvent, onEndEvent, instantEventCallback, startTime, endTime); + root.totalTime -= root.selfTime; + root.selfTime = 0; + return root; +} + +/** + * @param {!WebInspector.TimelineProfileTree.Node} topDownTree + * @param {?function(!WebInspector.TimelineProfileTree.Node):!WebInspector.TimelineProfileTree.Node=} groupingCallback + * @return {!WebInspector.TimelineProfileTree.Node} + */ +WebInspector.TimelineProfileTree.buildBottomUp = function(topDownTree, groupingCallback) +{ + var buRoot = new WebInspector.TimelineProfileTree.Node(); + buRoot.selfTime = 0; + buRoot.totalTime = 0; + buRoot.name = WebInspector.UIString("Bottom-Up Chart"); + /** @type {!Map} */ + buRoot.children = new Map(); + var nodesOnStack = /** @type {!Set} */ (new Set()); + if (topDownTree.children) + topDownTree.children.forEach(processNode); + buRoot.totalTime = topDownTree.totalTime; + + /** + * @param {!WebInspector.TimelineProfileTree.Node} tdNode + */ + function processNode(tdNode) + { + var buParent = groupingCallback && groupingCallback(tdNode) || buRoot; + if (buParent !== buRoot) { + buRoot.children.set(buParent.id, buParent); + buParent.parent = buRoot; + } + appendNode(tdNode, buParent); + var hadNode = nodesOnStack.has(tdNode.id); + if (!hadNode) + nodesOnStack.add(tdNode.id); + if (tdNode.children) + tdNode.children.forEach(processNode); + if (!hadNode) + nodesOnStack.delete(tdNode.id); + } + + /** + * @param {!WebInspector.TimelineProfileTree.Node} tdNode + * @param {!WebInspector.TimelineProfileTree.Node} buParent + */ + function appendNode(tdNode, buParent) + { + var selfTime = tdNode.selfTime; + var totalTime = tdNode.totalTime; + buParent.selfTime += selfTime; + buParent.totalTime += selfTime; + while (tdNode.parent) { + if (!buParent.children) + buParent.children = /** @type {!Map} */ (new Map()); + var id = tdNode.id; + var buNode = buParent.children.get(id); + if (!buNode) { + buNode = new WebInspector.TimelineProfileTree.Node(); + buNode.selfTime = selfTime; + buNode.totalTime = totalTime; + buNode.name = tdNode.name; + buNode.event = tdNode.event; + buNode.id = id; + buNode.parent = buParent; + buParent.children.set(id, buNode); + } else { + buNode.selfTime += selfTime; + if (!nodesOnStack.has(id)) + buNode.totalTime += totalTime; + } + tdNode = tdNode.parent; + buParent = buNode; + } + } + + // Purge zero self time nodes. + var rootChildren = buRoot.children; + for (var item of rootChildren.entries()) { + if (item[1].selfTime === 0) + rootChildren.delete(item[0]); + } + + return buRoot; + +} diff --git a/front_end/timeline/TimelineTreeView.js b/front_end/timeline/TimelineTreeView.js index 68f328cf33..404a6b609e 100644 --- a/front_end/timeline/TimelineTreeView.js +++ b/front_end/timeline/TimelineTreeView.js @@ -41,33 +41,10 @@ WebInspector.TimelineTreeView = function(model) this._splitWidget.setSidebarWidget(this._detailsView); this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._updateDetailsForSelection, this); - /** @type {?WebInspector.TimelineTreeView.ProfileTreeNode|undefined} */ + /** @type {?WebInspector.TimelineProfileTree.Node|undefined} */ this._lastSelectedNode; } -/** - * @constructor - */ -WebInspector.TimelineTreeView.ProfileTreeNode = function() -{ - /** @type {number} */ - this.totalTime; - /** @type {number} */ - this.selfTime; - /** @type {string} */ - this.name; - /** @type {string} */ - this.color; - /** @type {string} */ - this.id; - /** @type {!WebInspector.TracingModel.Event} */ - this.event; - /** @type {?Map} */ - this.children; - /** @type {?WebInspector.TimelineTreeView.ProfileTreeNode} */ - this.parent; -} - WebInspector.TimelineTreeView.prototype = { /** * @param {!WebInspector.TimelineSelection} selection @@ -102,7 +79,7 @@ WebInspector.TimelineTreeView.prototype = { _populateToolbar: function(parent) { }, /** - * @param {?WebInspector.TimelineTreeView.ProfileTreeNode} node + * @param {?WebInspector.TimelineProfileTree.Node} node */ _onHover: function(node) { }, @@ -116,7 +93,7 @@ WebInspector.TimelineTreeView.prototype = { }, /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} treeNode + * @param {!WebInspector.TimelineProfileTree.Node} treeNode * @param {boolean} suppressSelectedEvent */ selectProfileNode: function(treeNode, suppressSelectedEvent) @@ -159,7 +136,7 @@ WebInspector.TimelineTreeView.prototype = { }, /** - * @return {!WebInspector.TimelineTreeView.ProfileTreeNode} + * @return {!WebInspector.TimelineProfileTree.Node} */ _buildTree: function() { @@ -168,70 +145,11 @@ WebInspector.TimelineTreeView.prototype = { /** * @param {function(!WebInspector.TracingModel.Event):(string|symbol)=} eventIdCallback - * @return {!WebInspector.TimelineTreeView.ProfileTreeNode} + * @return {!WebInspector.TimelineProfileTree.Node} */ _buildTopDownTree: function(eventIdCallback) { - // Temporarily deposit a big enough value that exceeds the max recording time. - var /** @const */ initialTime = 1e7; - var root = new WebInspector.TimelineTreeView.ProfileTreeNode(); - root.totalTime = initialTime; - root.selfTime = initialTime; - root.name = WebInspector.UIString("Top-Down Chart"); - root.children = /** @type {!Map} */ (new Map()); - var parent = root; - - /** - * @this {WebInspector.TimelineTreeView} - * @param {!WebInspector.TracingModel.Event} e - */ - function onStartEvent(e) - { - if (!WebInspector.TimelineModel.isVisible(this._filters, e)) - return; - var time = e.endTime ? Math.min(this._endTime, e.endTime) - Math.max(this._startTime, e.startTime) : 0; - var id = eventIdCallback ? eventIdCallback(e) : Symbol("uniqueEventId"); - if (!parent.children) - parent.children = /** @type {!Map} */ (new Map()); - var node = parent.children.get(id); - if (node) { - node.selfTime += time; - node.totalTime += time; - } else { - node = new WebInspector.TimelineTreeView.ProfileTreeNode(); - node.totalTime = time; - node.selfTime = time; - node.parent = parent; - node.id = id; - node.event = e; - parent.children.set(id, node); - } - parent.selfTime -= time; - if (parent.selfTime < 0) { - console.log("Error: Negative self of " + parent.selfTime, e); - parent.selfTime = 0; - } - if (e.endTime) - parent = node; - } - - /** - * @this {WebInspector.TimelineTreeView} - * @param {!WebInspector.TracingModel.Event} e - */ - function onEndEvent(e) - { - if (!WebInspector.TimelineModel.isVisible(this._filters, e)) - return; - parent = parent.parent; - } - - var instantEventCallback = eventIdCallback ? undefined : onStartEvent.bind(this); // Ignore instant events when aggregating. - var events = this._model.mainThreadEvents(); - WebInspector.TimelineModel.forEachEvent(events, onStartEvent.bind(this), onEndEvent.bind(this), instantEventCallback, this._startTime, this._endTime); - root.totalTime -= root.selfTime; - root.selfTime = 0; - return root; + return WebInspector.TimelineProfileTree.buildTopDown(this._model.mainThreadEvents(), this._filters, this._startTime, this._endTime, eventIdCallback) }, /** @@ -324,7 +242,7 @@ WebInspector.TimelineTreeView.prototype = { }, /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} node + * @param {!WebInspector.TimelineProfileTree.Node} node * @return {boolean} */ _showDetailsForNode: function(node) @@ -348,7 +266,7 @@ WebInspector.TimelineTreeView.prototype = { }, /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} treeNode + * @param {!WebInspector.TimelineProfileTree.Node} treeNode * @return {?WebInspector.TimelineTreeView.GridNode} */ _dataGridNodeForTreeNode: function(treeNode) @@ -403,14 +321,23 @@ WebInspector.TimelineTreeView.eventStackFrame = function(event) */ WebInspector.TimelineTreeView.eventURL = function(event) { + var data = event.args["data"] || event.args["beginData"]; + if (data && data["url"]) + return data["url"]; var frame = WebInspector.TimelineTreeView.eventStackFrame(event); - return frame && frame["url"] || null; + while (frame) { + var url = frame["url"]; + if (url) + return url; + frame = frame.parent; + } + return null; } /** * @constructor * @extends {WebInspector.SortableDataGridNode} - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} profileNode + * @param {!WebInspector.TimelineProfileTree.Node} profileNode * @param {number} grandTotalTime * @param {number} maxSelfTime * @param {number} maxTotalTime @@ -521,7 +448,7 @@ WebInspector.TimelineTreeView.GridNode.prototype = { /** * @constructor * @extends {WebInspector.TimelineTreeView.GridNode} - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} profileNode + * @param {!WebInspector.TimelineProfileTree.Node} profileNode * @param {number} grandTotalTime * @param {number} maxSelfTime * @param {number} maxTotalTime @@ -642,8 +569,8 @@ WebInspector.AggregatedTimelineTreeView.prototype = { }, /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} treeNode - * @return {!Array} + * @param {!WebInspector.TimelineProfileTree.Node} treeNode + * @return {!Array} */ _buildHeaviestStack: function(treeNode) { @@ -684,9 +611,9 @@ WebInspector.AggregatedTimelineTreeView.prototype = { }, /** - * @param {function(!WebInspector.TimelineTreeView.ProfileTreeNode):string} nodeToGroupId - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} node - * @return {!WebInspector.TimelineTreeView.ProfileTreeNode} + * @param {function(!WebInspector.TimelineProfileTree.Node):string} nodeToGroupId + * @param {!WebInspector.TimelineProfileTree.Node} node + * @return {!WebInspector.TimelineProfileTree.Node} */ _nodeToGroupNode: function(nodeToGroupId, node) { @@ -697,11 +624,11 @@ WebInspector.AggregatedTimelineTreeView.prototype = { /** * @param {string} id * @param {!WebInspector.TracingModel.Event} event - * @return {!WebInspector.TimelineTreeView.ProfileTreeNode} + * @return {!WebInspector.TimelineProfileTree.Node} */ _buildGroupNode: function(id, event) { - var groupNode = new WebInspector.TimelineTreeView.ProfileTreeNode(); + var groupNode = new WebInspector.TimelineProfileTree.Node(); groupNode.id = id; groupNode.selfTime = 0; groupNode.totalTime = 0; @@ -725,12 +652,12 @@ WebInspector.AggregatedTimelineTreeView.prototype = { }, /** - * @return {?function(!WebInspector.TimelineTreeView.ProfileTreeNode):string} + * @return {?function(!WebInspector.TimelineProfileTree.Node):string} */ _nodeToGroupIdFunction: function() { /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} node + * @param {!WebInspector.TimelineProfileTree.Node} node * @return {string} */ function groupByCategory(node) @@ -739,7 +666,7 @@ WebInspector.AggregatedTimelineTreeView.prototype = { } /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} node + * @param {!WebInspector.TimelineProfileTree.Node} node * @return {string} */ function groupByURL(node) @@ -749,7 +676,7 @@ WebInspector.AggregatedTimelineTreeView.prototype = { /** * @param {boolean} groupSubdomains - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} node + * @param {!WebInspector.TimelineProfileTree.Node} node * @return {string} */ function groupByDomain(groupSubdomains, node) @@ -778,7 +705,7 @@ WebInspector.AggregatedTimelineTreeView.prototype = { for (var context of target.runtimeModel.executionContexts()) executionContextNamesByOrigin.set(context.origin, context.name); } - var groupByMap = /** @type {!Map} */ (new Map([ + var groupByMap = /** @type {!Map} */ (new Map([ [WebInspector.AggregatedTimelineTreeView.GroupBy.None, null], [WebInspector.AggregatedTimelineTreeView.GroupBy.Category, groupByCategory], [WebInspector.AggregatedTimelineTreeView.GroupBy.Subdomain, groupByDomain.bind(null, false)], @@ -790,7 +717,7 @@ WebInspector.AggregatedTimelineTreeView.prototype = { /** * @override - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} node + * @param {!WebInspector.TimelineProfileTree.Node} node * @return {boolean} */ _showDetailsForNode: function(node) @@ -818,7 +745,7 @@ WebInspector.CallTreeTimelineTreeView = function(model) WebInspector.CallTreeTimelineTreeView.prototype = { /** * @override - * @return {!WebInspector.TimelineTreeView.ProfileTreeNode} + * @return {!WebInspector.TimelineProfileTree.Node} */ _buildTree: function() { @@ -827,8 +754,8 @@ WebInspector.CallTreeTimelineTreeView.prototype = { }, /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} topDownTree - * @return {!WebInspector.TimelineTreeView.ProfileTreeNode} + * @param {!WebInspector.TimelineProfileTree.Node} topDownTree + * @return {!WebInspector.TimelineProfileTree.Node} */ _performTopDownTreeGrouping: function(topDownTree) { @@ -866,7 +793,7 @@ WebInspector.BottomUpTimelineTreeView = function(model) WebInspector.BottomUpTimelineTreeView.prototype = { /** * @override - * @return {!WebInspector.TimelineTreeView.ProfileTreeNode} + * @return {!WebInspector.TimelineProfileTree.Node} */ _buildTree: function() { @@ -874,89 +801,7 @@ WebInspector.BottomUpTimelineTreeView.prototype = { this._groupNodes = new Map(); var nodeToGroupId = this._nodeToGroupIdFunction(); var nodeToGroupNode = nodeToGroupId ? this._nodeToGroupNode.bind(this, nodeToGroupId) : null; - return this._buildBottomUpTree(topDown, nodeToGroupNode); - }, - - /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} topDownTree - * @param {?function(!WebInspector.TimelineTreeView.ProfileTreeNode):!WebInspector.TimelineTreeView.ProfileTreeNode=} groupingCallback - * @return {!WebInspector.TimelineTreeView.ProfileTreeNode} - */ - _buildBottomUpTree: function(topDownTree, groupingCallback) - { - var buRoot = new WebInspector.TimelineTreeView.ProfileTreeNode(); - buRoot.selfTime = 0; - buRoot.totalTime = 0; - buRoot.name = WebInspector.UIString("Bottom-Up Chart"); - /** @type {!Map} */ - buRoot.children = new Map(); - var nodesOnStack = /** @type {!Set} */ (new Set()); - if (topDownTree.children) - topDownTree.children.forEach(processNode); - buRoot.totalTime = topDownTree.totalTime; - - /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} tdNode - */ - function processNode(tdNode) - { - var buParent = groupingCallback && groupingCallback(tdNode) || buRoot; - if (buParent !== buRoot) { - buRoot.children.set(buParent.id, buParent); - buParent.parent = buRoot; - } - appendNode(tdNode, buParent); - var hadNode = nodesOnStack.has(tdNode.id); - if (!hadNode) - nodesOnStack.add(tdNode.id); - if (tdNode.children) - tdNode.children.forEach(processNode); - if (!hadNode) - nodesOnStack.delete(tdNode.id); - } - - /** - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} tdNode - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} buParent - */ - function appendNode(tdNode, buParent) - { - var selfTime = tdNode.selfTime; - var totalTime = tdNode.totalTime; - buParent.selfTime += selfTime; - buParent.totalTime += selfTime; - while (tdNode.parent) { - if (!buParent.children) - buParent.children = /** @type {!Map} */ (new Map()); - var id = tdNode.id; - var buNode = buParent.children.get(id); - if (!buNode) { - buNode = new WebInspector.TimelineTreeView.ProfileTreeNode(); - buNode.selfTime = selfTime; - buNode.totalTime = totalTime; - buNode.name = tdNode.name; - buNode.event = tdNode.event; - buNode.id = id; - buNode.parent = buParent; - buParent.children.set(id, buNode); - } else { - buNode.selfTime += selfTime; - if (!nodesOnStack.has(id)) - buNode.totalTime += totalTime; - } - tdNode = tdNode.parent; - buParent = buNode; - } - } - - // Purge zero self time nodes. - var rootChildren = buRoot.children; - for (var item of rootChildren.entries()) { - if (item[1].selfTime === 0) - rootChildren.delete(item[0]); - } - - return buRoot; + return WebInspector.TimelineProfileTree.buildBottomUp(topDown, nodeToGroupNode); }, __proto__: WebInspector.AggregatedTimelineTreeView.prototype @@ -994,7 +839,7 @@ WebInspector.EventsTimelineTreeView.prototype = { /** * @override - * @return {!WebInspector.TimelineTreeView.ProfileTreeNode} + * @return {!WebInspector.TimelineProfileTree.Node} */ _buildTree: function() { @@ -1012,7 +857,7 @@ WebInspector.EventsTimelineTreeView.prototype = { /** * @param {!WebInspector.TracingModel.Event} event - * @return {?WebInspector.TimelineTreeView.ProfileTreeNode} + * @return {?WebInspector.TimelineProfileTree.Node} */ _findNodeWithEvent: function(event) { @@ -1024,7 +869,7 @@ WebInspector.EventsTimelineTreeView.prototype = { iterators.pop(); continue; } - var child = /** @type {!WebInspector.TimelineTreeView.ProfileTreeNode} */ (iterator.value); + var child = /** @type {!WebInspector.TimelineProfileTree.Node} */ (iterator.value); if (child.event === event) return child; if (child.children) @@ -1070,7 +915,7 @@ WebInspector.EventsTimelineTreeView.prototype = { /** * @override - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} node + * @param {!WebInspector.TimelineProfileTree.Node} node * @return {boolean} */ _showDetailsForNode: function(node) @@ -1093,7 +938,7 @@ WebInspector.EventsTimelineTreeView.prototype = { /** * @override - * @param {?WebInspector.TimelineTreeView.ProfileTreeNode} node + * @param {?WebInspector.TimelineProfileTree.Node} node */ _onHover: function(node) { @@ -1132,8 +977,8 @@ WebInspector.TimelineStackView.Events = { WebInspector.TimelineStackView.prototype = { /** - * @param {!Array} stack - * @param {!WebInspector.TimelineTreeView.ProfileTreeNode} selectedNode + * @param {!Array} stack + * @param {!WebInspector.TimelineProfileTree.Node} selectedNode */ setStack: function(stack, selectedNode) { @@ -1151,7 +996,7 @@ WebInspector.TimelineStackView.prototype = { }, /** - * @return {?WebInspector.TimelineTreeView.ProfileTreeNode} + * @return {?WebInspector.TimelineProfileTree.Node} */ selectedTreeNode: function() { diff --git a/front_end/timeline/module.json b/front_end/timeline/module.json index f90cdd8ace..6ffb537628 100644 --- a/front_end/timeline/module.json +++ b/front_end/timeline/module.json @@ -109,6 +109,7 @@ "TimelineModel.js", "TimelineIRModel.js", "TimelineJSProfile.js", + "TimelineLoader.js", "TimelineFrameModel.js", "TimelineEventOverview.js", "TimelineFlameChart.js", @@ -116,6 +117,7 @@ "TimelineUIUtils.js", "TimelineLayersView.js", "TimelinePaintProfilerView.js", + "TimelineProfileTree.js", "TransformController.js", "PaintProfilerView.js", "TimelinePanel.js" diff --git a/front_end/ui/Toolbar.js b/front_end/ui/Toolbar.js index 64b1078552..bdc958fd25 100644 --- a/front_end/ui/Toolbar.js +++ b/front_end/ui/Toolbar.js @@ -637,8 +637,13 @@ WebInspector.ToolbarMenuButton.prototype = { * @override * @param {!Event} event */ - _clicked: function(event) + _mouseDown: function(event) { + if (event.buttons !== 1) { + WebInspector.ToolbarButton.prototype._mouseDown.call(this, event); + return; + } + var contextMenu = new WebInspector.ContextMenu(event, this._useSoftMenu, this.element.totalOffsetLeft(), @@ -647,6 +652,14 @@ WebInspector.ToolbarMenuButton.prototype = { contextMenu.show(); }, + /** + * @override + * @param {!Event} event + */ + _clicked: function(event) + { + }, + __proto__: WebInspector.ToolbarButton.prototype } diff --git a/front_end/ui/toolbar.css b/front_end/ui/toolbar.css index 15f9ba3b9f..45d3c61b75 100644 --- a/front_end/ui/toolbar.css +++ b/front_end/ui/toolbar.css @@ -128,7 +128,7 @@ /* Button */ -.toolbar-button:disabled .toolbar-glyph { +.toolbar-button:disabled { opacity: 0.5; } diff --git a/front_end/ui_lazy/FilteredListWidget.js b/front_end/ui_lazy/FilteredListWidget.js index bc80127976..b2e26fa4bd 100644 --- a/front_end/ui_lazy/FilteredListWidget.js +++ b/front_end/ui_lazy/FilteredListWidget.js @@ -296,13 +296,13 @@ WebInspector.FilteredListWidget.prototype = { switch (event.keyCode) { case WebInspector.KeyboardShortcut.Keys.Down.code: if (++newSelectedIndex >= this._filteredItems.length) - newSelectedIndex = this._filteredItems.length - 1; + newSelectedIndex = 0; this._updateSelection(newSelectedIndex, true); event.consume(true); break; case WebInspector.KeyboardShortcut.Keys.Up.code: if (--newSelectedIndex < 0) - newSelectedIndex = 0; + newSelectedIndex = this._filteredItems.length - 1; this._updateSelection(newSelectedIndex, false); event.consume(true); break; diff --git a/front_end/ui_lazy/FlameChart.js b/front_end/ui_lazy/FlameChart.js index 3697986342..3cc6b6d5ee 100644 --- a/front_end/ui_lazy/FlameChart.js +++ b/front_end/ui_lazy/FlameChart.js @@ -54,15 +54,13 @@ WebInspector.FlameChartDelegate.prototype = { * @extends {WebInspector.HBox} * @param {!WebInspector.FlameChartDataProvider} dataProvider * @param {!WebInspector.FlameChartDelegate} flameChartDelegate - * @param {boolean} isTopDown */ -WebInspector.FlameChart = function(dataProvider, flameChartDelegate, isTopDown) +WebInspector.FlameChart = function(dataProvider, flameChartDelegate) { WebInspector.HBox.call(this, true); this.registerRequiredCSS("ui_lazy/flameChart.css"); this.contentElement.classList.add("flame-chart-main-pane"); this._flameChartDelegate = flameChartDelegate; - this._isTopDown = isTopDown; this._calculator = new WebInspector.FlameChart.Calculator(); @@ -93,19 +91,18 @@ WebInspector.FlameChart = function(dataProvider, flameChartDelegate, isTopDown) this._windowLeft = 0.0; this._windowRight = 1.0; - this._windowWidth = 1.0; this._timeWindowLeft = 0; this._timeWindowRight = Infinity; this._barHeight = dataProvider.barHeight(); - this._barHeightDelta = this._isTopDown ? -this._barHeight : this._barHeight; this._paddingLeft = this._dataProvider.paddingLeft(); - this._markerPadding = 2; - this._markerRadius = this._barHeight / 2 - this._markerPadding; + var markerPadding = 2; + this._markerRadius = this._barHeight / 2 - markerPadding; this._highlightedMarkerIndex = -1; this._highlightedEntryIndex = -1; this._selectedEntryIndex = -1; this._rawTimelineDataLength = 0; - this._textWidth = {}; + /** @type {!Map>} */ + this._textWidth = new Map(); this._lastMouseOffsetX = 0; } @@ -121,17 +118,24 @@ WebInspector.FlameChartDataProvider = function() { } +/** + * @typedef {!{name: string, startLevel: number, expanded: boolean}} + */ +WebInspector.FlameChart.Group; + /** * @constructor - * @param {!Array.|!Uint8Array} entryLevels - * @param {!Array.|!Float32Array} entryTotalTimes - * @param {!Array.|!Float64Array} entryStartTimes + * @param {!Array|!Uint8Array} entryLevels + * @param {!Array|!Float32Array} entryTotalTimes + * @param {!Array|!Float64Array} entryStartTimes + * @param {?Array} groups */ -WebInspector.FlameChart.TimelineData = function(entryLevels, entryTotalTimes, entryStartTimes) +WebInspector.FlameChart.TimelineData = function(entryLevels, entryTotalTimes, entryStartTimes, groups) { this.entryLevels = entryLevels; this.entryTotalTimes = entryTotalTimes; this.entryStartTimes = entryStartTimes; + this.groups = groups; /** @type {!Array.} */ this.markers = []; this.flowStartTimes = []; @@ -146,6 +150,11 @@ WebInspector.FlameChartDataProvider.prototype = { */ barHeight: function() { }, + /** + * @return {number} + */ + groupSeparatorHeight: function() { }, + /** * @param {number} startTime * @param {number} endTime @@ -545,10 +554,10 @@ WebInspector.FlameChart.prototype = { var minEntryTimeWindow = Math.min(entryTotalTime, timeRight - timeLeft); var y = this._levelToHeight(timelineData.entryLevels[entryIndex]); - if (y < this._vScrollElement.scrollTop) + if (this._vScrollElement.scrollTop > y) this._vScrollElement.scrollTop = y; - else if (y > this._vScrollElement.scrollTop + this._offsetHeight + this._barHeightDelta) - this._vScrollElement.scrollTop = y - this._offsetHeight - this._barHeightDelta; + else if (this._vScrollElement.scrollTop < y - this._offsetHeight + this._barHeight) + this._vScrollElement.scrollTop = y - this._offsetHeight + this._barHeight; if (timeLeft > entryEndTime) { var delta = timeLeft - entryEndTime + minEntryTimeWindow; @@ -804,7 +813,7 @@ WebInspector.FlameChart.prototype = { this._entryInfo.style.top = y + "px"; }, - _onClick: function() + _onClick: function(event) { this.focus(); // onClick comes after dragStart and dragEnd events. @@ -813,6 +822,16 @@ WebInspector.FlameChart.prototype = { const clickThreshold = 5; if (this._maxDragOffset() > clickThreshold) return; + var groupIndex = this._coordinatesToGroupIndex(event.offsetX, event.offsetY); + if (groupIndex >= 0) { + var group = this._rawTimelineData.groups[groupIndex]; + group.expanded = !group.expanded; + this._updateLevelPositions(); + this._updateHeight(); + this._resetCanvas(); + this._draw(this._offsetWidth, this._offsetHeight); + return; + } this._hideRangeSelection(); this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, this._highlightedEntryIndex); }, @@ -901,12 +920,9 @@ WebInspector.FlameChart.prototype = { return; } if (e.keyCode === keys.Up.code || e.keyCode === keys.Down.code) { - var level = timelineData.entryLevels[this._selectedEntryIndex]; - var delta = e.keyCode === keys.Up.code ? 1 : -1; e.consume(true); - if (this._isTopDown) - delta = -delta; - level += delta; + var level = timelineData.entryLevels[this._selectedEntryIndex]; + level += e.keyCode === keys.Up.code ? -1 : 1; if (level < 0 || level >= this._timelineLevels.length) return; var entryTime = timelineData.entryStartTimes[this._selectedEntryIndex] + timelineData.entryTotalTimes[this._selectedEntryIndex] / 2; @@ -1016,15 +1032,12 @@ WebInspector.FlameChart.prototype = { if (!timelineData) return -1; var cursorTime = this._cursorTime(x); - var cursorLevel; - var offsetFromLevel; - if (this._isTopDown) { - cursorLevel = Math.floor((y - WebInspector.FlameChart.DividersBarHeight) / this._barHeight); - offsetFromLevel = y - WebInspector.FlameChart.DividersBarHeight - cursorLevel * this._barHeight; - } else { - cursorLevel = Math.floor((this._canvas.height / window.devicePixelRatio - y) / this._barHeight); - offsetFromLevel = this._canvas.height / window.devicePixelRatio - cursorLevel * this._barHeight; - } + var cursorLevel = this._visibleLevelOffsets.upperBound(y) - 1; + if (cursorLevel < 0) + return -1; + var offsetFromLevel = y - this._visibleLevelOffsets[cursorLevel]; + if (offsetFromLevel > this._barHeight) + return -1; var entryStartTimes = timelineData.entryStartTimes; var entryTotalTimes = timelineData.entryTotalTimes; var entryIndexes = this._timelineLevels[cursorLevel]; @@ -1072,6 +1085,22 @@ WebInspector.FlameChart.prototype = { return -1; }, + /** + * @param {number} x + * @param {number} y + * @return {number} + */ + _coordinatesToGroupIndex: function(x, y) + { + if (x < 0 || y < 0) + return -1; + y += this._scrollTop; + var group = this._groupOffsets.upperBound(y) - 1; + if (group >= 0 && y - this._groupOffsets[group] < this._barHeight) + return group; + return -1; + }, + /** * @param {number} x * @return {number} @@ -1131,9 +1160,11 @@ WebInspector.FlameChart.prototype = { context.save(); var ratio = window.devicePixelRatio; context.scale(ratio, ratio); + context.translate(0, -this._scrollTop); + context.font = "11px " + WebInspector.fontFamily(); var timeWindowRight = this._timeWindowRight; - var timeWindowLeft = this._timeWindowLeft - this._paddingLeftTime; + var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._timeToPixel; var entryTotalTimes = timelineData.entryTotalTimes; var entryStartTimes = timelineData.entryStartTimes; var entryLevels = timelineData.entryLevels; @@ -1143,24 +1174,24 @@ WebInspector.FlameChart.prototype = { var markerIndices = new Uint32Array(entryTotalTimes.length); var nextMarkerIndex = 0; var textPadding = this._dataProvider.textPadding(); - this._minTextWidth = 2 * textPadding + this._measureWidth(context, "\u2026"); - var minTextWidth = this._minTextWidth; + var minTextWidth = 2 * textPadding + this._measureWidth(context, "\u2026"); var unclippedWidth = width - (WebInspector.isMac() ? 0 : this._vScrollElement.offsetWidth); - var barHeight = this._barHeight; - - var colorBuckets = {}; - var minVisibleBarLevel = Math.max(Math.floor((this._scrollTop - this._baseHeight) / barHeight), 0); - var maxVisibleBarLevel = Math.min(Math.floor((this._scrollTop - this._baseHeight + height) / barHeight), this._dataProvider.maxStackDepth()); - - context.translate(0, -this._scrollTop); + var top = this._scrollTop; + var minVisibleBarLevel = Math.max(this._visibleLevelOffsets.upperBound(top) - 1, 0); function comparator(time, entryIndex) { return time - entryStartTimes[entryIndex]; } - for (var level = minVisibleBarLevel; level <= maxVisibleBarLevel; ++level) { + var colorBuckets = {}; + for (var level = minVisibleBarLevel; level < this._dataProvider.maxStackDepth(); ++level) { + if (this._levelToHeight(level) > top + height) + break; + if (!this._visibleLevels[level]) + continue; + // Entries are ordered by start time within a level, so find the last visible entry. var levelIndexes = this._timelineLevels[level]; var rightIndexOnLevel = levelIndexes.lowerBound(timeWindowRight, comparator) - 1; @@ -1264,6 +1295,7 @@ WebInspector.FlameChart.prototype = { var offsets = this._dataProvider.dividerOffsets(this._calculator.minimumBoundary(), this._calculator.maximumBoundary()); WebInspector.TimelineGrid.drawCanvasGrid(this._canvas, this._calculator, offsets); this._drawMarkers(); + this._drawGroupHeaders(width, height); this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex); this._updateElementPosition(this._selectedElement, this._selectedEntryIndex); @@ -1271,6 +1303,205 @@ WebInspector.FlameChart.prototype = { this._updateRangeSelectionOverlay(); }, + /** + * @param {number} width + * @param {number} height + */ + _drawGroupHeaders: function(width, height) + { + var context = this._canvas.getContext("2d"); + var top = this._scrollTop; + var ratio = window.devicePixelRatio; + var barHeight = this._barHeight; + var textBaseHeight = barHeight - this._dataProvider.textBaseline(); + var separatorHeight = this._dataProvider.groupSeparatorHeight(); + var groups = this._rawTimelineData.groups || []; + var groupOffsets = this._groupOffsets; + var lastGroupOffset = Array.prototype.peekLast.call(groupOffsets); + + var firstVisibleGroup = Math.max(groupOffsets.upperBound(top) - 1, 0); + var firstSeparator = Math.max(firstVisibleGroup, 1); + + context.save(); + context.scale(ratio, ratio); + context.translate(0, -top); + + context.fillStyle = "#eee"; + forEachGroup(firstSeparator, offset => context.fillRect(0, offset - separatorHeight + 2, width, separatorHeight - 4)); + if (groups.length && lastGroupOffset < top + height) + context.fillRect(0, lastGroupOffset + 2, width, top + height - lastGroupOffset) + + context.strokeStyle = "#ddd"; + context.beginPath(); + forEachGroup(firstSeparator, offset => { + hLine(offset - separatorHeight + 1.5); + hLine(offset - 1.5); + }); + hLine(lastGroupOffset + 1.5); + context.stroke(); + + context.strokeStyle = "#bbb"; + context.beginPath(); + forEachGroup(firstSeparator, offset => { + hLine(offset - separatorHeight + 0.5); + hLine(offset - 0.5); + }); + hLine(lastGroupOffset + 0.5); + context.stroke(); + + forEachGroup(firstVisibleGroup, (offset, index, group) => { + if (group.expanded) + return; + var endLevel = index + 1 < groups.length ? groups[index + 1].startLevel : this._dataProvider.maxStackDepth(); + this._drawCollapsedOverviewForGroup(offset + 1, group.startLevel, endLevel); + }); + + var headerLeftPadding = 6; + var arrowSide = 8; + var expansionArrowX = headerLeftPadding + arrowSide / 2; + context.font = "11px " + WebInspector.fontFamily(); + + context.save(); + context.fillStyle = "rgba(255, 255, 255, 0.5)"; + forEachGroup(firstVisibleGroup, drawBackground.bind(this)); + context.restore(); + + context.fillStyle = "#6e6e6e"; + context.beginPath(); + forEachGroup(firstVisibleGroup, (offset, index, group) => drawExpansionArrow(expansionArrowX, offset + textBaseHeight - arrowSide / 2, group.expanded)); + context.fill(); + + context.fillStyle = "#222"; + forEachGroup(firstVisibleGroup, (offset, index, group) => + context.fillText(group.name, Math.floor(expansionArrowX + arrowSide), offset + textBaseHeight)); + + context.strokeStyle = "#ddd"; + context.beginPath(); + forEachGroup(firstVisibleGroup, (offset, index, group) => { + if (group.expanded) + hLine(offset + barHeight - 0.5); + }); + context.stroke(); + + context.restore(); + + /** + * @param {number} y + */ + function hLine(y) + { + context.moveTo(0, y); + context.lineTo(width, y); + } + + /** + * @param {number} offset + * @param {number} index + * @param {!WebInspector.FlameChart.Group} group + * @this {WebInspector.FlameChart} + */ + function drawBackground(offset, index, group) + { + var vPadding = 2; + var hPadding = 3; + var width = this._measureWidth(context, group.name) + 1.5 * arrowSide + 2 * hPadding; + context.fillRect(headerLeftPadding - hPadding, offset + vPadding, width, barHeight - 2 * vPadding); + } + + /** + * @param {number} x + * @param {number} y + * @param {boolean} expanded + */ + function drawExpansionArrow(x, y, expanded) + { + var arrowHeight = arrowSide * Math.sqrt(3) / 2; + var arrowCenterOffset = Math.round(arrowHeight / 2); + context.save(); + context.translate(x, y); + context.rotate(expanded ? Math.PI / 2 : 0); + context.moveTo(-arrowCenterOffset, -arrowSide / 2); + context.lineTo(-arrowCenterOffset, arrowSide / 2); + context.lineTo(arrowHeight - arrowCenterOffset, 0); + context.restore(); + } + + /** + * @param {number} start + * @param {function(number, number, !WebInspector.FlameChart.Group)} callback + */ + function forEachGroup(start, callback) + { + for (var i = start; i < groups.length; ++i) { + var groupTop = groupOffsets[i]; + if (groupTop - separatorHeight > top + height) + break; + callback(groupTop, i, groups[i]); + } + } + }, + + /** + * @param {number} y + * @param {number} startLevel + * @param {number} endLevel + */ + _drawCollapsedOverviewForGroup: function(y, startLevel, endLevel) + { + var range = new SegmentedRange(mergeCallback); + var timeWindowRight = this._timeWindowRight; + var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._timeToPixel; + var context = this._canvas.getContext("2d"); + var barHeight = this._barHeight - 2; + var entryStartTimes = this._rawTimelineData.entryStartTimes; + var entryTotalTimes = this._rawTimelineData.entryTotalTimes; + + for (var level = startLevel; level < endLevel; ++level) { + var levelIndexes = this._timelineLevels[level]; + var rightIndexOnLevel = levelIndexes.lowerBound(timeWindowRight, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1; + var lastDrawOffset = Infinity; + + for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) { + var entryIndex = levelIndexes[entryIndexOnLevel]; + var entryStartTime = entryStartTimes[entryIndex]; + var startPosition = this._timeToPositionClipped(entryStartTime); + var entryEndTime = entryStartTime + entryTotalTimes[entryIndex]; + if (isNaN(entryEndTime) || startPosition >= lastDrawOffset) + continue; + if (entryEndTime <= timeWindowLeft) + break; + lastDrawOffset = startPosition; + var color = this._dataProvider.entryColor(entryIndex); + range.append(new Segment(startPosition, this._timeToPositionClipped(entryEndTime), color)); + } + } + + var segments = range.segments().slice().sort((a, b) => a.data.localeCompare(b.data)); + var lastColor; + context.beginPath(); + for (var i = 0; i < segments.length; ++i) { + var segment = segments[i]; + if (lastColor !== segments[i].data) { + context.fill(); + context.beginPath(); + lastColor = segments[i].data; + context.fillStyle = lastColor; + } + context.rect(segment.begin, y, segment.end - segment.begin, barHeight); + } + context.fill(); + + /** + * @param {!Segment} a + * @param {!Segment} b + * @return {?Segment} + */ + function mergeCallback(a, b) + { + return a.data === b.data && a.end + 0.4 > b.end ? a : null; + } + }, + /** * @param {!CanvasRenderingContext2D} context * @param {number} height @@ -1367,11 +1598,17 @@ WebInspector.FlameChart.prototype = { { if (!timelineData) { this._timelineLevels = null; + this._visibleLevelOffsets = null; + this._visibleLevels = null; + this._groupOffsets = null; this._rawTimelineData = null; this._rawTimelineDataLength = 0; return; } + this._rawTimelineData = timelineData; + this._rawTimelineDataLength = timelineData.entryStartTimes.length; + var entryCounters = new Uint32Array(this._dataProvider.maxStackDepth() + 1); for (var i = 0; i < timelineData.entryLevels.length; ++i) ++entryCounters[timelineData.entryLevels[i]]; @@ -1385,8 +1622,36 @@ WebInspector.FlameChart.prototype = { levelIndexes[level][entryCounters[level]++] = i; } this._timelineLevels = levelIndexes; - this._rawTimelineData = timelineData; - this._rawTimelineDataLength = timelineData.entryStartTimes.length; + this._updateLevelPositions(); + }, + + _updateLevelPositions: function() + { + var levelCount = this._timelineLevels.length; + var groups = this._rawTimelineData.groups || []; + this._visibleLevelOffsets = new Uint32Array(levelCount); + this._visibleLevels = new Uint8Array(levelCount); + this._groupOffsets = new Uint32Array(groups.length + 1); + + var separatorHeight = this._dataProvider.groupSeparatorHeight(); + var groupIndex = -1; + var currentOffset = WebInspector.FlameChart.DividersBarHeight; + var visible = true; + for (var level = 0; level < levelCount; ++level) { + if (groupIndex < groups.length - 1 && level === groups[groupIndex + 1].startLevel) { + currentOffset += groupIndex >= 0 ? separatorHeight : 0; + ++groupIndex; + this._groupOffsets[groupIndex] = currentOffset; + visible = groups[groupIndex].expanded; + currentOffset += this._barHeight; + } + this._visibleLevels[level] = visible; + this._visibleLevelOffsets[level] = currentOffset; + if (visible) + currentOffset += this._barHeight; + } + if (groupIndex >= 0) + this._groupOffsets[groupIndex + 1] = currentOffset; }, /** @@ -1459,7 +1724,7 @@ WebInspector.FlameChart.prototype = { */ _levelToHeight: function(level) { - return this._baseHeight - level * this._barHeightDelta; + return this._visibleLevelOffsets[level]; }, /** @@ -1528,15 +1793,15 @@ WebInspector.FlameChart.prototype = { return context.measureText(text).width; var font = context.font; - var textWidths = this._textWidth[font]; + var textWidths = this._textWidth.get(font); if (!textWidths) { - textWidths = {}; - this._textWidth[font] = textWidths; + textWidths = new Map(); + this._textWidth.set(font, textWidths); } - var width = textWidths[text]; + var width = textWidths.get(text); if (!width) { width = context.measureText(text).width; - textWidths[text] = width; + textWidths.set(text, width); } return width; }, @@ -1546,33 +1811,32 @@ WebInspector.FlameChart.prototype = { this._totalTime = this._dataProvider.totalTime(); this._minimumBoundary = this._dataProvider.minimumBoundary(); + var windowWidth = 1; if (this._timeWindowRight !== Infinity) { this._windowLeft = (this._timeWindowLeft - this._minimumBoundary) / this._totalTime; this._windowRight = (this._timeWindowRight - this._minimumBoundary) / this._totalTime; - this._windowWidth = this._windowRight - this._windowLeft; + windowWidth = this._windowRight - this._windowLeft; } else if (this._timeWindowLeft === Infinity) { this._windowLeft = Infinity; this._windowRight = Infinity; - this._windowWidth = 1; } else { this._windowLeft = 0; this._windowRight = 1; - this._windowWidth = 1; } - this._pixelWindowWidth = this._offsetWidth - this._paddingLeft; - this._totalPixels = Math.floor(this._pixelWindowWidth / this._windowWidth); - this._pixelWindowLeft = Math.floor(this._totalPixels * this._windowLeft); + var totalPixels = Math.floor((this._offsetWidth - this._paddingLeft) / windowWidth); + this._pixelWindowLeft = Math.floor(totalPixels * this._windowLeft); - this._timeToPixel = this._totalPixels / this._totalTime; - this._pixelToTime = this._totalTime / this._totalPixels; - this._paddingLeftTime = this._paddingLeft / this._timeToPixel; + this._timeToPixel = totalPixels / this._totalTime; + this._pixelToTime = this._totalTime / totalPixels; - this._baseHeight = this._isTopDown ? WebInspector.FlameChart.DividersBarHeight : this._offsetHeight - this._barHeight; + this._updateScrollBar(); + }, + _updateHeight: function() + { this._totalHeight = this._levelToHeight(this._dataProvider.maxStackDepth()); this._vScrollContent.style.height = this._totalHeight + "px"; - this._updateScrollBar(); }, onResize: function() @@ -1616,6 +1880,7 @@ WebInspector.FlameChart.prototype = { if (!this._timelineData()) return; this._resetCanvas(); + this._updateHeight(); this._updateBoundaries(); this._calculator._updateBoundaries(this); this._draw(this._offsetWidth, this._offsetHeight); @@ -1631,7 +1896,8 @@ WebInspector.FlameChart.prototype = { this._selectedEntryIndex = -1; this._rangeSelectionStart = 0; this._rangeSelectionEnd = 0; - this._textWidth = {}; + /** @type {!Map>} */ + this._textWidth = new Map(); this.update(); }, diff --git a/protocol.json b/protocol.json index 2bfc10dc2f..6587842360 100644 --- a/protocol.json +++ b/protocol.json @@ -234,7 +234,6 @@ }, { "name": "getNavigationHistory", - "parameters": [], "returns": [ { "name": "currentIndex", "type": "integer", "description": "Index of the current navigation history entry." }, { "name": "entries", "type": "array", "items": { "$ref": "NavigationEntry" }, "description": "Array of navigation history entries." } @@ -337,7 +336,8 @@ { "name": "screenWidth", "type": "integer", "optional": true, "description": "Overriding screen width value in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." }, { "name": "screenHeight", "type": "integer", "optional": true, "description": "Overriding screen height value in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." }, { "name": "positionX", "type": "integer", "optional": true, "description": "Overriding view X position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." }, - { "name": "positionY", "type": "integer", "optional": true, "description": "Overriding view Y position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." } + { "name": "positionY", "type": "integer", "optional": true, "description": "Overriding view Y position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." }, + { "name": "screenOrientation", "$ref": "Emulation.ScreenOrientation", "optional": true, "description": "Screen orientation override." } ], "handlers": ["browser"], "redirect": "Emulation", @@ -399,7 +399,6 @@ "name": "captureScreenshot", "async": true, "description": "Capture page screenshot.", - "parameters": [], "returns": [ { "name": "data", "type": "string", "description": "Base64-encoded image data (PNG)." } ], @@ -634,6 +633,13 @@ "parameters": [ { "name": "show", "type": "boolean", "description": "True for showing scroll bottleneck rects" } ] + }, + { + "name": "setShowViewportSizeOnResize", + "description": "Paints viewport size upon main frame resize.", + "parameters": [ + { "name": "show", "type": "boolean", "description": "Whether to paint size or not." } + ] } ] }, @@ -641,6 +647,17 @@ "domain": "Emulation", "description": "This domain emulates different environments for the page.", "hidden": true, + "types": [ + { + "id": "ScreenOrientation", + "type": "object", + "description": "Screen orientation.", + "properties": [ + { "name": "type", "type": "string", "enum": ["portraitPrimary", "portraitSecondary", "landscapePrimary", "landscapeSecondary"], "description": "Orientation type." }, + { "name": "angle", "type": "integer", "description": "Orientation angle." } + ] + } + ], "commands": [ { "name": "setDeviceMetricsOverride", @@ -657,7 +674,8 @@ { "name": "screenWidth", "type": "integer", "optional": true, "description": "Overriding screen width value in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." }, { "name": "screenHeight", "type": "integer", "optional": true, "description": "Overriding screen height value in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." }, { "name": "positionX", "type": "integer", "optional": true, "description": "Overriding view X position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." }, - { "name": "positionY", "type": "integer", "optional": true, "description": "Overriding view Y position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." } + { "name": "positionY", "type": "integer", "optional": true, "description": "Overriding view Y position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|." }, + { "name": "screenOrientation", "$ref": "ScreenOrientation", "optional": true, "description": "Screen orientation override." } ], "handlers": ["browser"] }, @@ -1014,7 +1032,8 @@ { "name": "scriptId", "$ref": "ScriptId", "description": "Id of the script to run." }, { "name": "executionContextId", "$ref": "ExecutionContextId", "description": "Specifies in which isolated context to perform script run. Each content script lives in an isolated context and this parameter is used to specify one of those contexts." }, { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." }, - { "name": "doNotPauseOnExceptionsAndMuteConsole", "type": "boolean", "optional": true, "description": "Specifies whether script run should stop on exceptions and mute console. Overrides setPauseOnException state." } + { "name": "doNotPauseOnExceptionsAndMuteConsole", "type": "boolean", "optional": true, "description": "Specifies whether script run should stop on exceptions and mute console. Overrides setPauseOnException state." }, + { "name": "includeCommandLineAPI", "type": "boolean", "optional": true, "description": "Determines whether Command Line API should be available during the evaluation." } ], "returns": [ { "name": "result", "$ref": "RemoteObject", "description": "Run result." }, @@ -1797,7 +1816,7 @@ "description": "Database with an array of object stores.", "properties": [ { "name": "name", "type": "string", "description": "Database name." }, - { "name": "intVersion", "type": "integer", "description": "Database version." }, + { "name": "version", "type": "integer", "description": "Database version." }, { "name": "objectStores", "type": "array", "items": { "$ref": "ObjectStore" }, "description": "Object stores in this database." } ] }, @@ -2182,109 +2201,6 @@ } ] }, - { - "domain": "FileSystem", - "hidden": true, - "types": [ - { - "id": "Entry", - "type": "object", - "properties": [ - { "name": "url", "type": "string", "description": "filesystem: URL for the entry." }, - { "name": "name", "type": "string", "description": "The name of the file or directory." }, - { "name": "isDirectory", "type": "boolean", "description": "True if the entry is a directory." }, - { "name": "mimeType", "type": "string", "optional": true, "description": "MIME type of the entry, available for a file only." }, - { "name": "resourceType", "$ref": "Page.ResourceType", "optional": true, "description": "ResourceType of the entry, available for a file only." }, - { "name": "isTextFile", "type": "boolean", "optional": true, "description": "True if the entry is a text file." } - ], - "description": "Represents a browser side file or directory." - }, - { - "id": "Metadata", - "type": "object", - "properties": [ - { "name": "modificationTime", "type": "number", "description": "Modification time." }, - { "name": "size", "type": "number", "description": "File size. This field is always zero for directories." } - ], - "description": "Represents metadata of a file or entry." - } - ], - "commands": [ - { - "name": "enable", - "description": "Enables events from backend." - }, - { - "name": "disable", - "description": "Disables events from backend." - }, - { - "name": "requestFileSystemRoot", - "async": true, - "parameters": [ - { "name": "origin", "type": "string", "description": "Security origin of requesting FileSystem. One of frames in current page needs to have this security origin." }, - { "name": "type", "type": "string", "enum": ["temporary", "persistent"], "description": "FileSystem type of requesting FileSystem." } - ], - "returns": [ - { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value." }, - { "name": "root", "$ref": "Entry", "optional": true, "description": "Contains root of the requested FileSystem if the command completed successfully." } - ], - "description": "Returns root directory of the FileSystem, if exists." - }, - { - "name": "requestDirectoryContent", - "async": true, - "parameters": [ - { "name": "url", "type": "string", "description": "URL of the directory that the frontend is requesting to read from." } - ], - "returns": [ - { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value." }, - { "name": "entries", "type": "array", "items": { "$ref": "Entry" }, "optional": true, "description": "Contains all entries on directory if the command completed successfully." } - ], - "description": "Returns content of the directory." - }, - { - "name": "requestMetadata", - "async": true, - "parameters": [ - { "name": "url", "type": "string", "description": "URL of the entry that the frontend is requesting to get metadata from." } - ], - "returns": [ - { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value." }, - { "name": "metadata", "$ref": "Metadata", "optional": true, "description": "Contains metadata of the entry if the command completed successfully." } - ], - "description": "Returns metadata of the entry." - }, - { - "name": "requestFileContent", - "async": true, - "parameters": [ - { "name": "url", "type": "string", "description": "URL of the file that the frontend is requesting to read from." }, - { "name": "readAsText", "type": "boolean", "description": "True if the content should be read as text, otherwise the result will be returned as base64 encoded text." }, - { "name": "start", "type": "integer", "optional": true, "description": "Specifies the start of range to read." }, - { "name": "end", "type": "integer", "optional": true, "description": "Specifies the end of range to read exclusively." }, - { "name": "charset", "type": "string", "optional": true, "description": "Overrides charset of the content when content is served as text." } - ], - "returns": [ - { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value." }, - { "name": "content", "type": "string", "optional": true, "description": "Content of the file." }, - { "name": "charset", "type": "string", "optional": true, "description": "Charset of the content if it is served as text." } - ], - "description": "Returns content of the file. Result should be sliced into [start, end)." - }, - { - "name": "deleteEntry", - "async": true, - "parameters": [ - { "name": "url", "type": "string", "description": "URL of the entry to delete." } - ], - "returns": [ - { "name": "errorCode", "type": "integer", "description": "0, if no error. Otherwise errorCode is set to FileError::ErrorCode value." } - ], - "description": "Deletes specified entry. If the entry is a directory, the agent deletes children recursively." - } - ] - }, { "domain": "DOM", "description": "This domain exposes DOM read/write operations. Each DOM Node is represented with its mirror object that has an id. This id can be used to get additional information on the Node, resolve it into the JavaScript object wrapper, etc. It is important that client receives DOM events only for the nodes that are known to the client. Backend keeps track of the nodes that were sent to the client and never sends the same node twice. It is client's responsibility to collect information about the nodes that were sent to the client.

Note that iframe owner elements will return corresponding document elements as their child nodes.

", @@ -3174,6 +3090,16 @@ { "name": "style", "$ref": "CSSStyle", "description": "Associated style declaration." } ], "description": "CSS keyframe rule representation." + }, + { + "id": "StyleDeclarationEdit", + "type": "object", + "properties": [ + { "name": "styleSheetId", "$ref": "StyleSheetId", "description": "The css style sheet identifier." }, + { "name": "range", "$ref": "SourceRange", "description": "The range of the style text in the enclosing stylesheet." }, + { "name": "text", "type": "string", "description": "New style text."} + ], + "description": "A descriptor of operation to mutate style declaration text." } ], "commands": [ @@ -3279,16 +3205,14 @@ "description": "Modifies the keyframe rule key text." }, { - "name": "setStyleText", + "name": "setStyleTexts", "parameters": [ - { "name": "styleSheetId", "$ref": "StyleSheetId" }, - { "name": "range", "$ref": "SourceRange" }, - { "name": "text", "type": "string" } + { "name": "edits", "type": "array", "items": { "$ref": "StyleDeclarationEdit" }} ], "returns": [ - { "name": "style", "$ref": "CSSStyle", "description": "The resulting style after the selector modification." } + { "name": "styles", "type": "array", "items": { "$ref": "CSSStyle" }, "description": "The resulting styles after modification." } ], - "description": "Modifies the style text." + "description": "Applies specified style edits one after another in the given order." }, { "name": "setMediaText", @@ -3903,7 +3827,8 @@ { "name": "isInternalScript", "type": "boolean", "optional": true, "description": "Determines whether this script is an internal script.", "hidden": true }, { "name": "isLiveEdit", "type": "boolean", "optional": true, "description": "True, if this script is generated as a result of the live edit operation.", "hidden": true }, { "name": "sourceMapURL", "type": "string", "optional": true, "description": "URL of source map associated with script (if any)." }, - { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "True, if this script has sourceURL.", "hidden": true } + { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "True, if this script has sourceURL.", "hidden": true }, + { "name": "deprecatedCommentWasUsed", "type": "boolean", "optional": true, "hidden": true, "description": "True, if '//@ sourceURL' or '//@ sourceMappingURL' was used."} ], "description": "Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger." }, @@ -3920,7 +3845,8 @@ { "name": "isContentScript", "type": "boolean", "optional": true, "description": "Determines whether this script is a user extension script." }, { "name": "isInternalScript", "type": "boolean", "optional": true, "description": "Determines whether this script is an internal script.", "hidden": true }, { "name": "sourceMapURL", "type": "string", "optional": true, "description": "URL of source map associated with script (if any)." }, - { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "True, if this script has sourceURL.", "hidden": true } + { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "True, if this script has sourceURL.", "hidden": true }, + { "name": "deprecatedCommentWasUsed", "type": "boolean", "optional": true, "hidden": true, "description": "True, if '//@ sourceURL' or '//@ sourceMappingURL' was used."} ], "description": "Fired when virtual machine fails to parse the script." }, @@ -4903,32 +4829,6 @@ } ] }, - { - "domain": "ScreenOrientation", - "hidden": true, - "types": [ - { - "id": "OrientationType", - "type": "string", - "enum": ["portraitPrimary", "portraitSecondary", "landscapePrimary", "landscapeSecondary"], - "description": "Orientation type" - } - ], - "commands": [ - { - "name": "setScreenOrientationOverride", - "description": "Overrides the Screen Orientation.", - "parameters": [ - { "name": "angle", "type": "integer", "description": "Orientation angle" }, - { "name": "type", "$ref": "OrientationType", "description": "Orientation type" } - ] - }, - { - "name": "clearScreenOrientationOverride", - "description": "Clears the overridden Screen Orientation." - } - ] - }, { "domain": "Tracing", "commands": [ @@ -5016,6 +4916,7 @@ "hidden": true, "properties": [ { "name": "id", "type": "string", "description": "Animation's id." }, + { "name": "name", "type": "string", "description": "Animation's name." }, { "name": "pausedState", "type": "boolean", "hidden": "true", "description": "Animation's internal paused state." }, { "name": "playState", "type": "string", "description": "Animation's play state." }, { "name": "playbackRate", "type": "number", "description": "Animation's playback rate." }, @@ -5040,7 +4941,6 @@ { "name": "duration", "type": "number", "description": "AnimationEffect's iteration duration." }, { "name": "direction", "type": "string", "description": "AnimationEffect's playback direction." }, { "name": "fill", "type": "string", "description": "AnimationEffect's fill mode." }, - { "name": "name", "type": "string", "description": "AnimationEffect's name." }, { "name": "backendNodeId", "$ref": "DOM.BackendNodeId", "description": "AnimationEffect's target node." }, { "name": "keyframesRule", "$ref": "KeyframesRule", "optional": true, "description": "AnimationEffect's keyframes." }, { "name": "easing", "type": "string", "description": "AnimationEffect's timing function." }