Web Inspector: provide a way to inject "bootstrap" JavaScript into the page as the...
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Oct 2019 06:54:49 +0000 (06:54 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Oct 2019 06:54:49 +0000 (06:54 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195847
<rdar://problem/48950551>

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

When debugging webpages, it's often useful to be able to swizzle various functions in order
to add extra logs for when they're called (e.g. `Event.prototype.preventDefault`). Sometimes
this can be difficult, such as if the page saves a copy of the function and references that
instead, in which case it would be helpful to have a way to guarantee that the swizzled code
is the first thing evaluated after the context is created.

This change adds support for that concept, which has been named Inspector Bootstrap Script.
Once created, it will be injected as the first user script to every new global object that
is created afterwards. Modifications to the Inspector Bootstrap Script take effect for all
new global objects created _after_ the modification happened.

* inspector/protocol/Page.json:
Add `setBoostrapScript` command.

Source/WebCore:

When debugging webpages, it's often useful to be able to swizzle various functions in order
to add extra logs for when they're called (e.g. `Event.prototype.preventDefault`). Sometimes
this can be difficult, such as if the page saves a copy of the function and references that
instead, in which case it would be helpful to have a way to guarantee that the swizzled code
is the first thing evaluated after the context is created.

This change adds support for that concept, which has been named Inspector Bootstrap Script.
Once created, it will be injected as the first user script to every new global object that
is created afterwards. Modifications to the Inspector Bootstrap Script take effect for all
new global objects created _after_ the modification happened.

Tests: inspector/page/setBootstrapScript-main-frame.html
       inspector/page/setBootstrapScript-sub-frame.html

* inspector/agents/InspectorPageAgent.h:
* inspector/agents/InspectorPageAgent.cpp:
(WebCore::InspectorPageAgent::setBootstrapScript): Added.
(WebCore::InspectorPageAgent::didClearWindowObjectInWorld): Added.

* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::didClearWindowObjectInWorldImpl):
* inspector/agents/page/PageDebuggerAgent.h:
* inspector/agents/page/PageDebuggerAgent.cpp:
(WebCore::PageDebuggerAgent::didClearWindowObjectInWorld): Added.
(WebCore::PageDebuggerAgent::didClearMainFrameWindowObject): Deleted.
* inspector/agents/page/PageRuntimeAgent.h:
* inspector/agents/page/PageRuntimeAgent.cpp:
(WebCore::PageRuntimeAgent::didClearWindowObjectInWorld): Added.
(WebCore::PageRuntimeAgent::didCreateMainWorldContext): Deleted.
Rename existing global object creation "notifications" for more consistency between agents.

Source/WebInspectorUI:

When debugging webpages, it's often useful to be able to swizzle various functions in order
to add extra logs for when they're called (e.g. `Event.prototype.preventDefault`). Sometimes
this can be difficult, such as if the page saves a copy of the function and references that
instead, in which case it would be helpful to have a way to guarantee that the swizzled code
is the first thing evaluated after the context is created.

This change adds support for that concept, which has been named Inspector Bootstrap Script.
Once created, it will be injected as the first user script to every new global object that
is created afterwards. Modifications to the Inspector Bootstrap Script take effect for all
new global objects created _after_ the modification happened.

* UserInterface/Controllers/NetworkManager.js:
(WI.NetworkManager):
(WI.NetworkManager.supportsBootstrapScript): Added.
(WI.NetworkManager.get bootstrapScriptURL): Added.
(WI.NetworkManager.get bootstrapScriptSourceObjectStoreKey): Added.
(WI.NetworkManager.prototype.initializeTarget):
(WI.NetworkManager.prototype.get bootstrapScript): Added.
(WI.NetworkManager.prototype.get bootstrapScriptEnabled): Added.
(WI.NetworkManager.prototype.set bootstrapScriptEnabled): Added.
(WI.NetworkManager.prototype.async createBootstrapScript): Added.
(WI.NetworkManager.prototype.destroyBootstrapScript): Added.
(WI.NetworkManager.prototype._processServiceWorkerConfiguration):
(WI.NetworkManager.prototype._handleBootstrapScriptContentDidChange): Added.

* UserInterface/Models/LocalScript.js:
(WI.LocalScript):
(WI.LocalScript.prototype.get editable): Added.
(WI.LocalScript.prototype.get supportsScriptBlackboxing): Added.
(WI.LocalScript.prototype.requestContentFromBackend):
(WI.LocalScript.prototype.handleCurrentRevisionContentChange): Added.
* UserInterface/Views/ScriptContentView.js:
(WI.ScriptContentView):
(WI.ScriptContentView.prototype._contentWillPopulate):
(WI.ScriptContentView.prototype._contentDidPopulate):
(WI.ScriptContentView.prototype._handleTextEditorContentDidChange): Added.
Support editing of `WI.LocalScript` that are specifically marked as such.

* UserInterface/Models/Script.js:
(WI.Script):
(WI.Script.prototype.get displayName):
(WI.Script.prototype.get displayURL):
(WI.Script.prototype.isMainResource):
(WI.Script.prototype._resolveResource):
* UserInterface/Views/SourceCodeTextEditor.js:
(WI.SourceCodeTextEditor.prototype.customPerformSearch):
(WI.SourceCodeTextEditor.prototype._createTypeTokenAnnotator):
(WI.SourceCodeTextEditor.prototype._createBasicBlockAnnotator):
Allow a `WI.Script` to not have an associated `WI.Target`, specifically for `WI.LocalScript`.

* UserInterface/Views/SourcesNavigationSidebarPanel.js:
(WI.SourcesNavigationSidebarPanel):
(WI.SourcesNavigationSidebarPanel.prototype.treeElementForRepresentedObject):
(WI.SourcesNavigationSidebarPanel.prototype.createContentTreeOutline):
(WI.SourcesNavigationSidebarPanel.prototype._compareTreeElements):
(WI.SourcesNavigationSidebarPanel.prototype._addLocalOverride): Added.
(WI.SourcesNavigationSidebarPanel.prototype._removeResourceOverride): Added.
(WI.SourcesNavigationSidebarPanel.prototype._populateCreateResourceContextMenu):
(WI.SourcesNavigationSidebarPanel.prototype._handleBootstrapScriptCreated): Added.
(WI.SourcesNavigationSidebarPanel.prototype._handleBootstrapScriptDestroyed): Added.
(WI.SourcesNavigationSidebarPanel.prototype._handleLocalResourceOverrideAdded):
(WI.SourcesNavigationSidebarPanel.prototype._handleLocalResourceOverrideRemoved):
(WI.SourcesNavigationSidebarPanel.prototype._handleDebuggerPaused):
(WI.SourcesNavigationSidebarPanel.prototype._handleDebuggerResumed):
(WI.SourcesNavigationSidebarPanel.prototype._addLocalResourceOverride): Removed.
(WI.SourcesNavigationSidebarPanel.prototype._removeLocalResourceOverride): Removed.
Add an item in the create resource context menu for creating/focusing the bootstrap script.

* UserInterface/Views/ScriptTreeElement.js:
(WI.ScriptTreeElement):
* UserInterface/Views/BootstrapScriptTreeElement.js: Added.
(WI.BootstrapScriptTreeElement):
(WI.BootstrapScriptTreeElement.prototype.onattach):
(WI.BootstrapScriptTreeElement.prototype.ondetach):
(WI.BootstrapScriptTreeElement.prototype.ondelete):
(WI.BootstrapScriptTreeElement.prototype.onspace):
(WI.BootstrapScriptTreeElement.prototype.canSelectOnMouseDown):
(WI.BootstrapScriptTreeElement.prototype.populateContextMenu):
(WI.BootstrapScriptTreeElement.prototype.updateStatus):
(WI.BootstrapScriptTreeElement.prototype._handleNetworkManagerBootstrapScriptToggled):
* UserInterface/Views/BootstrapScriptTreeElement.css: Added.
(.item.script.bootstrap .status > input[type="checkbox"]):
* UserInterface/Views/LocalResourceOverrideTreeElement.css:
(.item.resource.override .status > input[type="checkbox"]): Added.
(.item.resource.override .status > div): Removed.
Don't show the full bootstrap script URL. Instead, show "Inspector Bootstrap Script".

* UserInterface/Views/OpenResourceDialog.js:
(WI.OpenResourceDialog.prototype.didPresentDialog):
Show the bootstrap script in the open resource dialog.

* UserInterface/Base/Utilities.js:
(isWebInspectorBootstrapScript): Added.

* UserInterface/Controllers/DebuggerManager.js:
(WI.DebuggerManager.prototype.scriptDidFail):

* UserInterface/Base/ObjectStore.js:
(WI.ObjectStore.static _open):
(WI.ObjectStore.prototype.async get): Added.
Add a generalized object store that can be used for one-off values that need the larger
storage capacity of `IndexedDB`.

* .eslintrc:
* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* inspector/page/setBootstrapScript-main-frame.html: Added.
* inspector/page/setBootstrapScript-main-frame-expected.txt: Added.
* inspector/page/setBootstrapScript-sub-frame.html: Added.
* inspector/page/setBootstrapScript-sub-frame-expected.txt: Added.
* inspector/page/resources/bootstrap-iframe.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@251531 268f45cc-cd09-0410-ab3c-d52691b4dbfc

34 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/page/resources/bootstrap-iframe.html [new file with mode: 0644]
LayoutTests/inspector/page/setBootstrapScript-main-frame-expected.txt [new file with mode: 0644]
LayoutTests/inspector/page/setBootstrapScript-main-frame.html [new file with mode: 0644]
LayoutTests/inspector/page/setBootstrapScript-sub-frame-expected.txt [new file with mode: 0644]
LayoutTests/inspector/page/setBootstrapScript-sub-frame.html [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/Page.json
Source/WebCore/ChangeLog
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/agents/InspectorPageAgent.cpp
Source/WebCore/inspector/agents/InspectorPageAgent.h
Source/WebCore/inspector/agents/page/PageDebuggerAgent.cpp
Source/WebCore/inspector/agents/page/PageDebuggerAgent.h
Source/WebCore/inspector/agents/page/PageRuntimeAgent.cpp
Source/WebCore/inspector/agents/page/PageRuntimeAgent.h
Source/WebInspectorUI/.eslintrc
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/ObjectStore.js
Source/WebInspectorUI/UserInterface/Base/Utilities.js
Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/LocalScript.js
Source/WebInspectorUI/UserInterface/Models/Script.js
Source/WebInspectorUI/UserInterface/Views/BootstrapScriptTreeElement.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/BootstrapScriptTreeElement.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/LocalResourceOverrideTreeElement.css
Source/WebInspectorUI/UserInterface/Views/OpenResourceDialog.js
Source/WebInspectorUI/UserInterface/Views/ScriptContentView.js
Source/WebInspectorUI/UserInterface/Views/ScriptTreeElement.js
Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js
Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.js

index 1cd1fd3..b82b2cb 100644 (file)
@@ -1,3 +1,17 @@
+2019-10-23  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: provide a way to inject "bootstrap" JavaScript into the page as the first script executed
+        https://bugs.webkit.org/show_bug.cgi?id=195847
+        <rdar://problem/48950551>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/page/setBootstrapScript-main-frame.html: Added.
+        * inspector/page/setBootstrapScript-main-frame-expected.txt: Added.
+        * inspector/page/setBootstrapScript-sub-frame.html: Added.
+        * inspector/page/setBootstrapScript-sub-frame-expected.txt: Added.
+        * inspector/page/resources/bootstrap-iframe.html: Added.
+
 2019-10-23  Chris Dumez  <cdumez@apple.com>
 
         Notification should not prevent entering the back/forward cache
diff --git a/LayoutTests/inspector/page/resources/bootstrap-iframe.html b/LayoutTests/inspector/page/resources/bootstrap-iframe.html
new file mode 100644 (file)
index 0000000..810123d
--- /dev/null
@@ -0,0 +1,8 @@
+<html>
+<body>
+<script>
+if (!window.valueFromBootstrapScript)
+    TestPage.addResult("FAIL: Should have set 'window.valueFromBootstrapScript'.");
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/inspector/page/setBootstrapScript-main-frame-expected.txt b/LayoutTests/inspector/page/setBootstrapScript-main-frame-expected.txt
new file mode 100644 (file)
index 0000000..62cb864
--- /dev/null
@@ -0,0 +1,10 @@
+Tests for Page.setBootstrapScript command.
+
+
+== Running test suite: Page.setBootstrapScript
+-- Running test case: Page.setBootstrapScript.MainFrame
+PASS: 'valueFromBootstrapScript' should be 'undefined'.
+Setting bootstrap script...
+Reloading page...
+PASS: 'valueFromBootstrapScript' should be '42'.
+
diff --git a/LayoutTests/inspector/page/setBootstrapScript-main-frame.html b/LayoutTests/inspector/page/setBootstrapScript-main-frame.html
new file mode 100644 (file)
index 0000000..2c729c0
--- /dev/null
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Page.setBootstrapScript");
+
+    suite.addTestCase({
+        name: "Page.setBootstrapScript.MainFrame",
+        description: "Test that the bootstrap script is executed in the main frame.",
+        async test() {
+            let valueBeforeBootstrapScript = await InspectorTest.evaluateInPage(`window.valueFromBootstrapScript`);
+            InspectorTest.expectEqual(valueBeforeBootstrapScript, undefined, "'valueFromBootstrapScript' should be 'undefined'.");
+
+            InspectorTest.log("Setting bootstrap script...");
+            await WI.networkManager.createBootstrapScript();
+            WI.networkManager.bootstrapScriptEnabled = true;
+            WI.networkManager.bootstrapScript.currentRevision.updateRevisionContent(`window.valueFromBootstrapScript = 42;`);
+
+            InspectorTest.log("Reloading page...");
+            await InspectorTest.reloadPage();
+
+            await Promise.delay(10);
+
+            let valueAfterBootstrapScript = await InspectorTest.evaluateInPage(`window.valueFromBootstrapScript`);
+            InspectorTest.expectEqual(valueAfterBootstrapScript, 42, "'valueFromBootstrapScript' should be '42'.");
+        },
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Tests for Page.setBootstrapScript command.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/page/setBootstrapScript-sub-frame-expected.txt b/LayoutTests/inspector/page/setBootstrapScript-sub-frame-expected.txt
new file mode 100644 (file)
index 0000000..2324c59
--- /dev/null
@@ -0,0 +1,9 @@
+Tests for Page.setBootstrapScript command.
+
+
+== Running test suite: Page.setBootstrapScript
+-- Running test case: Page.setBootstrapScript.SubFrame
+Setting bootstrap script...
+Adding subframe...
+PASS: 'valueFromBootstrapScript' should be '42'.
+
diff --git a/LayoutTests/inspector/page/setBootstrapScript-sub-frame.html b/LayoutTests/inspector/page/setBootstrapScript-sub-frame.html
new file mode 100644 (file)
index 0000000..06f7f96
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function addSubframe() {
+    let iframe = document.body.appendChild(document.createElement("iframe"));
+    iframe.src = "resources/bootstrap-iframe.html";
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Page.setBootstrapScript");
+
+    suite.addTestCase({
+        name: "Page.setBootstrapScript.SubFrame",
+        description: "Test that the bootstrap script is executed in all sub frames.",
+        async test() {
+            InspectorTest.log("Setting bootstrap script...");
+            await WI.networkManager.createBootstrapScript();
+            WI.networkManager.bootstrapScriptEnabled = true;
+            WI.networkManager.bootstrapScript.currentRevision.updateRevisionContent(`window.valueFromBootstrapScript = 42;`);
+
+            InspectorTest.log("Adding subframe...");
+            let [frameWasAddedEvent] = await Promise.all([
+                WI.networkManager.awaitEvent(WI.NetworkManager.Event.FrameWasAdded),
+                InspectorTest.evaluateInPage(`addSubframe()`),
+            ]);
+
+            let {frame} = frameWasAddedEvent.data;
+
+            await frame.awaitEvent(WI.Frame.Event.PageExecutionContextChanged);
+
+            let {result} = await RuntimeAgent.evaluate.invoke({
+                expression: `window.valueFromBootstrapScript`,
+                contextId: frame.pageExecutionContext.id,
+                returnByValue: true,
+            });
+            InspectorTest.expectEqual(result.value, 42, "'valueFromBootstrapScript' should be '42'.");
+        },
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Tests for Page.setBootstrapScript command.</p>
+</body>
+</html>
index cc8459e..bc3a407 100644 (file)
@@ -1,3 +1,25 @@
+2019-10-23  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: provide a way to inject "bootstrap" JavaScript into the page as the first script executed
+        https://bugs.webkit.org/show_bug.cgi?id=195847
+        <rdar://problem/48950551>
+
+        Reviewed by Joseph Pecoraro.
+
+        When debugging webpages, it's often useful to be able to swizzle various functions in order
+        to add extra logs for when they're called (e.g. `Event.prototype.preventDefault`). Sometimes
+        this can be difficult, such as if the page saves a copy of the function and references that
+        instead, in which case it would be helpful to have a way to guarantee that the swizzled code
+        is the first thing evaluated after the context is created.
+
+        This change adds support for that concept, which has been named Inspector Bootstrap Script.
+        Once created, it will be injected as the first user script to every new global object that
+        is created afterwards. Modifications to the Inspector Bootstrap Script take effect for all
+        new global objects created _after_ the modification happened.
+
+        * inspector/protocol/Page.json:
+        Add `setBoostrapScript` command.
+
 2019-10-23  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] Remove wasmAwareLexicalGlobalObject
index ea90c2f..367d1f2 100644 (file)
             ]
         },
         {
+            "name": "setBootstrapScript",
+            "parameters": [
+                { "name": "source", "type": "string", "optional": true, "description": "If `source` is provided (and not empty), it will be injected into all future global objects as soon as they're created. Omitting `source` will stop this from happening." }
+            ]
+        },
+        {
             "name": "searchInResource",
             "description": "Searches for given string in resource content.",
             "parameters": [
index 348cbee..f6eb088 100644 (file)
@@ -1,3 +1,42 @@
+2019-10-23  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: provide a way to inject "bootstrap" JavaScript into the page as the first script executed
+        https://bugs.webkit.org/show_bug.cgi?id=195847
+        <rdar://problem/48950551>
+
+        Reviewed by Joseph Pecoraro.
+
+        When debugging webpages, it's often useful to be able to swizzle various functions in order
+        to add extra logs for when they're called (e.g. `Event.prototype.preventDefault`). Sometimes
+        this can be difficult, such as if the page saves a copy of the function and references that
+        instead, in which case it would be helpful to have a way to guarantee that the swizzled code
+        is the first thing evaluated after the context is created.
+
+        This change adds support for that concept, which has been named Inspector Bootstrap Script.
+        Once created, it will be injected as the first user script to every new global object that
+        is created afterwards. Modifications to the Inspector Bootstrap Script take effect for all
+        new global objects created _after_ the modification happened.
+
+        Tests: inspector/page/setBootstrapScript-main-frame.html
+               inspector/page/setBootstrapScript-sub-frame.html
+
+        * inspector/agents/InspectorPageAgent.h:
+        * inspector/agents/InspectorPageAgent.cpp:
+        (WebCore::InspectorPageAgent::setBootstrapScript): Added.
+        (WebCore::InspectorPageAgent::didClearWindowObjectInWorld): Added.
+
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::didClearWindowObjectInWorldImpl):
+        * inspector/agents/page/PageDebuggerAgent.h:
+        * inspector/agents/page/PageDebuggerAgent.cpp:
+        (WebCore::PageDebuggerAgent::didClearWindowObjectInWorld): Added.
+        (WebCore::PageDebuggerAgent::didClearMainFrameWindowObject): Deleted.
+        * inspector/agents/page/PageRuntimeAgent.h:
+        * inspector/agents/page/PageRuntimeAgent.cpp:
+        (WebCore::PageRuntimeAgent::didClearWindowObjectInWorld): Added.
+        (WebCore::PageRuntimeAgent::didCreateMainWorldContext): Deleted.
+        Rename existing global object creation "notifications" for more consistency between agents.
+
 2019-10-23  Peng Liu  <peng.liu6@apple.com>
 
         [Picture-in-Picture Web API] Implement EnterPictureInPictureEvent support
index 3e741cd..717f01a 100644 (file)
@@ -118,15 +118,17 @@ static Frame* frameForScriptExecutionContext(ScriptExecutionContext& context)
 
 void InspectorInstrumentation::didClearWindowObjectInWorldImpl(InstrumentingAgents& instrumentingAgents, Frame& frame, DOMWrapperWorld& world)
 {
-    if (auto* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent()) {
-        if (&world == &mainThreadNormalWorld() && frame.isMainFrame())
-            pageDebuggerAgent->didClearMainFrameWindowObject();
-    }
+    if (&world != &mainThreadNormalWorld())
+        return;
 
-    if (PageRuntimeAgent* pageRuntimeAgent = instrumentingAgents.pageRuntimeAgent()) {
-        if (&world == &mainThreadNormalWorld())
-            pageRuntimeAgent->didCreateMainWorldContext(frame);
-    }
+    if (auto* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent())
+        pageDebuggerAgent->didClearWindowObjectInWorld(frame);
+
+    if (auto* pageRuntimeAgent = instrumentingAgents.pageRuntimeAgent())
+        pageRuntimeAgent->didClearWindowObjectInWorld(frame);
+
+    if (auto* pageAgent = instrumentingAgents.inspectorPageAgent())
+        pageAgent->didClearWindowObjectInWorld(frame);
 }
 
 bool InspectorInstrumentation::isDebuggerPausedImpl(InstrumentingAgents& instrumentingAgents)
index b8ab9b6..f2e228b 100644 (file)
@@ -58,6 +58,7 @@
 #include "RenderObject.h"
 #include "RenderTheme.h"
 #include "ScriptController.h"
+#include "ScriptSourceCode.h"
 #include "SecurityOrigin.h"
 #include "Settings.h"
 #include "StyleScope.h"
@@ -581,6 +582,11 @@ void InspectorPageAgent::getResourceContent(ErrorString& errorString, const Stri
     resourceContent(errorString, frame, URL({ }, url), content, base64Encoded);
 }
 
+void InspectorPageAgent::setBootstrapScript(ErrorString&, const String* optionalSource)
+{
+    m_bootstrapScript = optionalSource ? *optionalSource : nullString();
+}
+
 void InspectorPageAgent::searchInResource(ErrorString& errorString, const String& frameId, const String& url, const String& query, const bool* optionalCaseSensitive, const bool* optionalIsRegex, const String* optionalRequestId, RefPtr<JSON::ArrayOf<Inspector::Protocol::GenericTypes::SearchMatch>>& results)
 {
     results = JSON::ArrayOf<Inspector::Protocol::GenericTypes::SearchMatch>::create();
@@ -760,6 +766,14 @@ void InspectorPageAgent::defaultAppearanceDidChange(bool useDarkAppearance)
     m_frontendDispatcher->defaultAppearanceDidChange(useDarkAppearance ? Inspector::Protocol::Page::Appearance::Dark : Inspector::Protocol::Page::Appearance::Light);
 }
 
+void InspectorPageAgent::didClearWindowObjectInWorld(Frame& frame)
+{
+    if (m_bootstrapScript.isEmpty())
+        return;
+
+    frame.script().evaluate(ScriptSourceCode(m_bootstrapScript, URL { URL(), "web-inspector://bootstrap.js"_s }));
+}
+
 void InspectorPageAgent::didPaint(RenderObject& renderer, const LayoutRect& rect)
 {
     if (!m_showPaintRects)
index 8c786ec..4fd8c0b 100644 (file)
@@ -103,6 +103,7 @@ public:
     void deleteCookie(ErrorString&, const String& cookieName, const String& url) override;
     void getResourceTree(ErrorString&, RefPtr<Inspector::Protocol::Page::FrameResourceTree>&) override;
     void getResourceContent(ErrorString&, const String& frameId, const String& url, String* content, bool* base64Encoded) override;
+    void setBootstrapScript(ErrorString&, const String* optionalSource) final;
     void searchInResource(ErrorString&, const String& frameId, const String& url, const String& query, const bool* optionalCaseSensitive, const bool* optionalIsRegex, const String* optionalRequestId, RefPtr<JSON::ArrayOf<Inspector::Protocol::GenericTypes::SearchMatch>>&) override;
     void searchInResources(ErrorString&, const String&, const bool* caseSensitive, const bool* isRegex, RefPtr<JSON::ArrayOf<Inspector::Protocol::Page::SearchResult>>&) override;
     void setShowRulers(ErrorString&, bool) override;
@@ -128,6 +129,7 @@ public:
     void defaultAppearanceDidChange(bool useDarkAppearance);
     void applyUserAgentOverride(String&);
     void applyEmulatedMedia(String&);
+    void didClearWindowObjectInWorld(Frame&);
     void didPaint(RenderObject&, const LayoutRect&);
     void didLayout();
     void didScroll();
@@ -160,6 +162,7 @@ private:
     String m_userAgentOverride;
     String m_emulatedMedia;
     String m_forcedAppearance;
+    String m_bootstrapScript;
     bool m_isFirstLayoutAfterOnLoad { false };
     bool m_showPaintRects { false };
 };
index a00e466..dad9945 100644 (file)
@@ -155,8 +155,11 @@ InjectedScript PageDebuggerAgent::injectedScriptForEval(ErrorString& errorString
     return injectedScript;
 }
 
-void PageDebuggerAgent::didClearMainFrameWindowObject()
+void PageDebuggerAgent::didClearWindowObjectInWorld(Frame& frame)
 {
+    if (!frame.isMainFrame())
+        return;
+
     didClearGlobalObject();
 }
 
index 7dbb094..10bcca6 100644 (file)
@@ -53,7 +53,7 @@ public:
     void breakpointActionLog(JSC::JSGlobalObject*, const String&) override;
 
     // InspectorInstrumentation
-    void didClearMainFrameWindowObject();
+    void didClearWindowObjectInWorld(Frame&);
     void mainFrameStartedLoading();
     void mainFrameStoppedLoading();
     void mainFrameNavigated();
index 6566ea0..f5dd183 100644 (file)
@@ -85,7 +85,7 @@ void PageRuntimeAgent::disable(ErrorString& errorString)
     InspectorRuntimeAgent::disable(errorString);
 }
 
-void PageRuntimeAgent::didCreateMainWorldContext(Frame& frame)
+void PageRuntimeAgent::didClearWindowObjectInWorld(Frame& frame)
 {
     auto* pageAgent = m_instrumentingAgents.inspectorPageAgent();
     if (!pageAgent)
index ab44a2b..ee2efe3 100644 (file)
@@ -59,7 +59,7 @@ public:
     void evaluate(ErrorString&, const String& expression, const String* objectGroup, const bool* includeCommandLineAPI, const bool* doNotPauseOnExceptionsAndMuteConsole, const int* executionContextId, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, const bool* emulateUserGesture, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result, Optional<bool>& wasThrown, Optional<int>& savedResultIndex) override;
 
     // InspectorInstrumentation
-    void didCreateMainWorldContext(Frame&);
+    void didClearWindowObjectInWorld(Frame&);
 
 private:
     Inspector::InjectedScript injectedScriptForEval(ErrorString&, const int* executionContextId) override;
index d9f947e..daa6148 100644 (file)
@@ -97,6 +97,7 @@
         "isEnterKey": true,
         "isFunctionStringNativeCode": true,
         "isTextLikelyMinified": true,
+        "isWebInspectorBootstrapScript": true,
         "isWebInspectorConsoleEvaluationScript": true,
         "isWebInspectorInternalScript": true,
         "isWebKitInternalScript": true,
index 6aee5bb..6d1474e 100644 (file)
@@ -1,3 +1,117 @@
+2019-10-23  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: provide a way to inject "bootstrap" JavaScript into the page as the first script executed
+        https://bugs.webkit.org/show_bug.cgi?id=195847
+        <rdar://problem/48950551>
+
+        Reviewed by Joseph Pecoraro.
+
+        When debugging webpages, it's often useful to be able to swizzle various functions in order
+        to add extra logs for when they're called (e.g. `Event.prototype.preventDefault`). Sometimes
+        this can be difficult, such as if the page saves a copy of the function and references that
+        instead, in which case it would be helpful to have a way to guarantee that the swizzled code
+        is the first thing evaluated after the context is created.
+
+        This change adds support for that concept, which has been named Inspector Bootstrap Script.
+        Once created, it will be injected as the first user script to every new global object that
+        is created afterwards. Modifications to the Inspector Bootstrap Script take effect for all
+        new global objects created _after_ the modification happened.
+
+        * UserInterface/Controllers/NetworkManager.js:
+        (WI.NetworkManager):
+        (WI.NetworkManager.supportsBootstrapScript): Added.
+        (WI.NetworkManager.get bootstrapScriptURL): Added.
+        (WI.NetworkManager.get bootstrapScriptSourceObjectStoreKey): Added.
+        (WI.NetworkManager.prototype.initializeTarget):
+        (WI.NetworkManager.prototype.get bootstrapScript): Added.
+        (WI.NetworkManager.prototype.get bootstrapScriptEnabled): Added.
+        (WI.NetworkManager.prototype.set bootstrapScriptEnabled): Added.
+        (WI.NetworkManager.prototype.async createBootstrapScript): Added.
+        (WI.NetworkManager.prototype.destroyBootstrapScript): Added.
+        (WI.NetworkManager.prototype._processServiceWorkerConfiguration):
+        (WI.NetworkManager.prototype._handleBootstrapScriptContentDidChange): Added.
+
+        * UserInterface/Models/LocalScript.js:
+        (WI.LocalScript):
+        (WI.LocalScript.prototype.get editable): Added.
+        (WI.LocalScript.prototype.get supportsScriptBlackboxing): Added.
+        (WI.LocalScript.prototype.requestContentFromBackend):
+        (WI.LocalScript.prototype.handleCurrentRevisionContentChange): Added.
+        * UserInterface/Views/ScriptContentView.js:
+        (WI.ScriptContentView):
+        (WI.ScriptContentView.prototype._contentWillPopulate):
+        (WI.ScriptContentView.prototype._contentDidPopulate):
+        (WI.ScriptContentView.prototype._handleTextEditorContentDidChange): Added.
+        Support editing of `WI.LocalScript` that are specifically marked as such.
+
+        * UserInterface/Models/Script.js:
+        (WI.Script):
+        (WI.Script.prototype.get displayName):
+        (WI.Script.prototype.get displayURL):
+        (WI.Script.prototype.isMainResource):
+        (WI.Script.prototype._resolveResource):
+        * UserInterface/Views/SourceCodeTextEditor.js:
+        (WI.SourceCodeTextEditor.prototype.customPerformSearch):
+        (WI.SourceCodeTextEditor.prototype._createTypeTokenAnnotator):
+        (WI.SourceCodeTextEditor.prototype._createBasicBlockAnnotator):
+        Allow a `WI.Script` to not have an associated `WI.Target`, specifically for `WI.LocalScript`.
+
+        * UserInterface/Views/SourcesNavigationSidebarPanel.js:
+        (WI.SourcesNavigationSidebarPanel):
+        (WI.SourcesNavigationSidebarPanel.prototype.treeElementForRepresentedObject):
+        (WI.SourcesNavigationSidebarPanel.prototype.createContentTreeOutline):
+        (WI.SourcesNavigationSidebarPanel.prototype._compareTreeElements):
+        (WI.SourcesNavigationSidebarPanel.prototype._addLocalOverride): Added.
+        (WI.SourcesNavigationSidebarPanel.prototype._removeResourceOverride): Added.
+        (WI.SourcesNavigationSidebarPanel.prototype._populateCreateResourceContextMenu):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleBootstrapScriptCreated): Added.
+        (WI.SourcesNavigationSidebarPanel.prototype._handleBootstrapScriptDestroyed): Added.
+        (WI.SourcesNavigationSidebarPanel.prototype._handleLocalResourceOverrideAdded):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleLocalResourceOverrideRemoved):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleDebuggerPaused):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleDebuggerResumed):
+        (WI.SourcesNavigationSidebarPanel.prototype._addLocalResourceOverride): Removed.
+        (WI.SourcesNavigationSidebarPanel.prototype._removeLocalResourceOverride): Removed.
+        Add an item in the create resource context menu for creating/focusing the bootstrap script.
+
+        * UserInterface/Views/ScriptTreeElement.js:
+        (WI.ScriptTreeElement):
+        * UserInterface/Views/BootstrapScriptTreeElement.js: Added.
+        (WI.BootstrapScriptTreeElement):
+        (WI.BootstrapScriptTreeElement.prototype.onattach):
+        (WI.BootstrapScriptTreeElement.prototype.ondetach):
+        (WI.BootstrapScriptTreeElement.prototype.ondelete):
+        (WI.BootstrapScriptTreeElement.prototype.onspace):
+        (WI.BootstrapScriptTreeElement.prototype.canSelectOnMouseDown):
+        (WI.BootstrapScriptTreeElement.prototype.populateContextMenu):
+        (WI.BootstrapScriptTreeElement.prototype.updateStatus):
+        (WI.BootstrapScriptTreeElement.prototype._handleNetworkManagerBootstrapScriptToggled):
+        * UserInterface/Views/BootstrapScriptTreeElement.css: Added.
+        (.item.script.bootstrap .status > input[type="checkbox"]):
+        * UserInterface/Views/LocalResourceOverrideTreeElement.css:
+        (.item.resource.override .status > input[type="checkbox"]): Added.
+        (.item.resource.override .status > div): Removed.
+        Don't show the full bootstrap script URL. Instead, show "Inspector Bootstrap Script".
+
+        * UserInterface/Views/OpenResourceDialog.js:
+        (WI.OpenResourceDialog.prototype.didPresentDialog):
+        Show the bootstrap script in the open resource dialog.
+
+        * UserInterface/Base/Utilities.js:
+        (isWebInspectorBootstrapScript): Added.
+
+        * UserInterface/Controllers/DebuggerManager.js:
+        (WI.DebuggerManager.prototype.scriptDidFail):
+
+        * UserInterface/Base/ObjectStore.js:
+        (WI.ObjectStore.static _open):
+        (WI.ObjectStore.prototype.async get): Added.
+        Add a generalized object store that can be used for one-off values that need the larger
+        storage capacity of `IndexedDB`.
+
+        * .eslintrc:
+        * Localizations/en.lproj/localizedStrings.js:
+
 2019-10-23  Yury Semikhatsky  <yurys@chromium.org>
 
         Web Inspector: notify inspector when provisional page is created, committed and destroyed
index 57256b5..42e43fa 100644 (file)
@@ -360,6 +360,7 @@ localizedStrings["Disable Encryption"] = "Disable Encryption";
 localizedStrings["Disable Event Listener"] = "Disable Event Listener";
 localizedStrings["Disable Event Listeners"] = "Disable Event Listeners";
 localizedStrings["Disable ICE Candidate Restrictions"] = "Disable ICE Candidate Restrictions";
+localizedStrings["Disable Inspector Bootstrap Script"] = "Disable Inspector Bootstrap Script";
 localizedStrings["Disable Local Override"] = "Disable Local Override";
 localizedStrings["Disable Program"] = "Disable Program";
 localizedStrings["Disable Rule"] = "Disable Rule";
@@ -438,6 +439,7 @@ localizedStrings["Enable Breakpoints"] = "Enable Breakpoints";
 localizedStrings["Enable Descendant Breakpoints"] = "Enable Descendant Breakpoints";
 localizedStrings["Enable Event Listener"] = "Enable Event Listener";
 localizedStrings["Enable Event Listeners"] = "Enable Event Listeners";
+localizedStrings["Enable Inspector Bootstrap Script"] = "Enable Inspector Bootstrap Script";
 localizedStrings["Enable Layers Tab"] = "Enable Layers Tab";
 localizedStrings["Enable Local Override"] = "Enable Local Override";
 localizedStrings["Enable New Tab Bar"] = "Enable New Tab Bar";
@@ -624,6 +626,7 @@ localizedStrings["Initial Velocity"] = "Initial Velocity";
 localizedStrings["Initiated"] = "Initiated";
 localizedStrings["Initiator"] = "Initiator";
 localizedStrings["Input: "] = "Input: ";
+localizedStrings["Inspector Bootstrap Script"] = "Inspector Bootstrap Script";
 localizedStrings["Inspector Override"] = "Inspector Override";
 localizedStrings["Inspector Style Sheet"] = "Inspector Style Sheet";
 localizedStrings["Instances"] = "Instances";
@@ -883,6 +886,7 @@ localizedStrings["Reload Web Inspector"] = "Reload Web Inspector";
 localizedStrings["Reload page (%s)\nReload page ignoring cache (%s)"] = "Reload page (%s)\nReload page ignoring cache (%s)";
 localizedStrings["Removals"] = "Removals";
 localizedStrings["Remove Blackbox"] = "Remove Blackbox";
+localizedStrings["Remove Inspector Bootstrap Script"] = "Remove Inspector Bootstrap Script";
 localizedStrings["Remove Local Override"] = "Remove Local Override";
 localizedStrings["Remove Watch Expression"] = "Remove Watch Expression";
 localizedStrings["Remove probe"] = "Remove probe";
index e0a27c8..5056f13 100644 (file)
@@ -67,7 +67,7 @@ WI.ObjectStore = class ObjectStore
 
         WI.ObjectStore._databaseCallbacks = [callback];
 
-        const version = 4; // Increment this for every edit to `WI.objectStores`.
+        const version = 5; // Increment this for every edit to `WI.objectStores`.
 
         let databaseRequest = window.indexedDB.open(WI.ObjectStore._databaseName, version);
         databaseRequest.addEventListener("upgradeneeded", (event) => {
@@ -119,6 +119,14 @@ WI.ObjectStore = class ObjectStore
         resolved.object[resolved.key] = value;
     }
 
+    async get(...args)
+    {
+        if (!WI.ObjectStore.supported())
+            return undefined;
+
+        return this._operation("readonly", (objectStore) => objectStore.get(...args));
+    }
+
     async getAll(...args)
     {
         if (!WI.ObjectStore.supported())
@@ -240,6 +248,7 @@ WI.ObjectStore.toJSONSymbol = Symbol("ObjectStore-toJSON");
 
 // Be sure to update the `version` above when making changes.
 WI.objectStores = {
+    general: new WI.ObjectStore("general"),
     audits: new WI.ObjectStore("audit-manager-tests", {keyPath: "__id", autoIncrement: true}),
     breakpoints: new WI.ObjectStore("debugger-breakpoints", {keyPath: "__id"}),
     domBreakpoints: new WI.ObjectStore("dom-debugger-dom-breakpoints", {keyPath: "__id"}),
index 637f2b1..f88c562 100644 (file)
@@ -1594,6 +1594,11 @@ function appendWebInspectorConsoleEvaluationSourceURL(string)
     return "\n//# sourceURL=__WebInspectorConsoleEvaluation__\n" + string;
 }
 
+function isWebInspectorBootstrapScript(url)
+{
+    return url === WI.NetworkManager.bootstrapScriptURL;
+}
+
 function isWebInspectorInternalScript(url)
 {
     return url === "__WebInspectorInternal__";
index 97fb0fe..3977e22 100644 (file)
@@ -900,8 +900,9 @@ WI.DebuggerManager = class DebuggerManager extends WI.Object
 
     scriptDidFail(target, url, scriptSource)
     {
+        const sourceURL = null;
         const sourceType = WI.Script.SourceType.Program;
-        let script = new WI.LocalScript(target, url, sourceType, scriptSource);
+        let script = new WI.LocalScript(target, url, sourceURL, sourceType, scriptSource);
 
         // If there is already a resource we don't need to have the script anymore,
         // we only need a script to use for parser error location links.
index eb9b9c3..8540bd2 100644 (file)
@@ -72,7 +72,18 @@ WI.NetworkManager = class NetworkManager extends WI.Object
                     this.addLocalResourceOverride(localResourceOverride);
                 }
                 this._restoringLocalResourceOverrides = false;
-            })());            
+            })());
+        }
+
+        this._bootstrapScript = null;
+        if (NetworkManager.supportsBootstrapScript()) {
+            this._bootstrapScriptEnabledSetting = new WI.Setting("bootstrap-script-enabled", true);
+
+            WI.Target.registerInitializationPromise((async () => {
+                let bootstrapScriptSource = await WI.objectStores.general.get(NetworkManager.bootstrapScriptSourceObjectStoreKey);
+                if (bootstrapScriptSource !== undefined)
+                    this.createBootstrapScript(bootstrapScriptSource);
+            })());
         }
     }
 
@@ -89,6 +100,21 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         return InspectorBackend.hasCommand("Network.setInterceptionEnabled");
     }
 
+    static supportsBootstrapScript()
+    {
+        return InspectorBackend.hasCommand("Page.setBootstrapScript");
+    }
+
+    static get bootstrapScriptURL()
+    {
+        return "web-inspector://bootstrap.js";
+    }
+
+    static get bootstrapScriptSourceObjectStoreKey()
+    {
+        return "bootstrap-script-source";
+    }
+
     static synthesizeImportError(message)
     {
         message = WI.UIString("HAR Import Error: %s").format(message);
@@ -111,6 +137,10 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         if (target.hasDomain("Page")) {
             target.PageAgent.enable();
             target.PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this));
+
+            // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet.
+            if (target.hasCommand("Page.setBootstrapScript") && this._bootstrapScript && this._bootstrapScriptEnabledSetting.value)
+                target.PageAgent.setBootstrapScript(this._bootstrapScript.content);
         }
 
         if (target.hasDomain("ServiceWorker"))
@@ -147,10 +177,8 @@ WI.NetworkManager = class NetworkManager extends WI.Object
 
     // Public
 
-    get mainFrame()
-    {
-        return this._mainFrame;
-    }
+    get mainFrame() { return this._mainFrame; }
+    get bootstrapScript() { return this._bootstrapScript; }
 
     get frames()
     {
@@ -233,6 +261,75 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         loadAndParseSourceMap();
     }
 
+    get bootstrapScriptEnabled()
+    {
+        console.assert(NetworkManager.supportsBootstrapScript());
+        console.assert(this._bootstrapScript);
+
+        return this._bootstrapScriptEnabledSetting.value;
+    }
+
+    set bootstrapScriptEnabled(enabled)
+    {
+        console.assert(NetworkManager.supportsBootstrapScript());
+        console.assert(this._bootstrapScript);
+
+        this._bootstrapScriptEnabledSetting.value = !!enabled;
+
+        let source = this._bootstrapScriptEnabledSetting.value ? this._bootstrapScript.content : undefined;
+
+        // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet.
+        for (let target of WI.targets) {
+            if (target.hasCommand("Page.setBootstrapScript"))
+                target.PageAgent.setBootstrapScript(source);
+        }
+
+        this.dispatchEventToListeners(NetworkManager.Event.BootstrapScriptEnabledChanged, {bootstrapScript: this._bootstrapScript});
+    }
+
+    async createBootstrapScript(source)
+    {
+        console.assert(NetworkManager.supportsBootstrapScript());
+
+        if (this._bootstrapScript)
+            return;
+
+        if (!arguments.length)
+            source = await WI.objectStores.general.get(NetworkManager.bootstrapScriptSourceObjectStoreKey);
+
+        const target = null;
+        const url = null;
+        const sourceURL = NetworkManager.bootstrapScriptURL;
+        this._bootstrapScript = new WI.LocalScript(target, url, sourceURL, WI.Script.SourceType.Program, source || "", {injected: true, editable: true});
+        this._bootstrapScript.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleBootstrapScriptContentDidChange, this);
+
+        if (!this._bootstrapScript.content)
+            WI.objectStores.general.put("", NetworkManager.bootstrapScriptSourceObjectStoreKey);
+
+        this.dispatchEventToListeners(NetworkManager.Event.BootstrapScriptCreated, {bootstrapScript: this._bootstrapScript});
+    }
+
+    destroyBootstrapScript()
+    {
+        console.assert(NetworkManager.supportsBootstrapScript());
+
+        if (!this._bootstrapScript)
+            return;
+
+        let bootstrapScript = this._bootstrapScript;
+
+        this._bootstrapScript = null;
+        WI.objectStores.general.delete(NetworkManager.bootstrapScriptSourceObjectStoreKey);
+
+        // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet.
+        for (let target of WI.targets) {
+            if (target.hasCommand("Page.setBootstrapScript"))
+                target.PageAgent.setBootstrapScript();
+        }
+
+        this.dispatchEventToListeners(NetworkManager.Event.BootstrapScriptDestroyed, {bootstrapScript});
+    }
+
     addLocalResourceOverride(localResourceOverride)
     {
         console.assert(localResourceOverride instanceof WI.LocalResourceOverride);
@@ -1015,8 +1112,9 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         WI.mainTarget.name = initializationPayload.url;
 
         // Create a main resource with this content in case the content never shows up as a WI.Script.
-        const type = WI.Script.SourceType.Program;
-        let script = new WI.LocalScript(WI.mainTarget, initializationPayload.url, type, initializationPayload.content);
+        const sourceURL = null;
+        const sourceType = WI.Script.SourceType.Program;
+        let script = new WI.LocalScript(WI.mainTarget, initializationPayload.url, sourceURL, sourceType, initializationPayload.content);
         WI.mainTarget.mainResource = script;
 
         InspectorBackend.runAfterPendingDispatches(() => {
@@ -1284,6 +1382,22 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         }
     }
 
+    _handleBootstrapScriptContentDidChange(event)
+    {
+        let source = this._bootstrapScript.content;
+
+        WI.objectStores.general.put(source, NetworkManager.bootstrapScriptSourceObjectStoreKey);
+
+        if (!this._bootstrapScriptEnabledSetting.value)
+            return;
+
+        // COMPATIBILITY (iOS 13.0): Page.setBootstrapScript did not exist yet.
+        for (let target of WI.targets) {
+            if (target.hasCommand("Page.setBootstrapScript"))
+                target.PageAgent.setBootstrapScript(source);
+        }
+    }
+
     _extraDomainsActivated(event)
     {
         let target = WI.assumingMainTarget();
@@ -1305,6 +1419,9 @@ WI.NetworkManager.Event = {
     FrameWasAdded: "network-manager-frame-was-added",
     FrameWasRemoved: "network-manager-frame-was-removed",
     MainFrameDidChange: "network-manager-main-frame-did-change",
+    BootstrapScriptCreated: "network-manager-bootstrap-script-created",
+    BootstrapScriptEnabledChanged: "network-manager-bootstrap-script-enabled-changed",
+    BootstrapScriptDestroyed: "network-manager-bootstrap-script-destroyed",
     LocalResourceOverrideAdded: "network-manager-local-resource-override-added",
     LocalResourceOverrideRemoved: "network-manager-local-resource-override-removed",
 };
index c80c56d..0854733 100644 (file)
@@ -38,6 +38,7 @@
     <link rel="stylesheet" href="Views/AuditTreeElement.css">
     <link rel="stylesheet" href="Views/BezierEditor.css">
     <link rel="stylesheet" href="Views/BlackboxSettingsView.css">
+    <link rel="stylesheet" href="Views/BootstrapScriptTreeElement.css">
     <link rel="stylesheet" href="Views/BoxModelDetailsSectionRow.css">
     <link rel="stylesheet" href="Views/BreakpointActionView.css">
     <link rel="stylesheet" href="Views/BreakpointPopoverController.css">
     <script src="Views/ElementsTabContentView.js"></script>
     <script src="Views/LayersTabContentView.js"></script>
     <script src="Views/ResourceTreeElement.js"></script>
+    <script src="Views/ScriptTreeElement.js"></script>
     <script src="Views/SearchTabContentView.js"></script>
     <script src="Views/SettingsTabContentView.js"></script>
     <script src="Views/SourcesTabContentView.js"></script>
     <script src="Views/AuditTreeElement.js"></script>
     <script src="Views/BezierEditor.js"></script>
     <script src="Views/BlackboxSettingsView.js"></script>
+    <script src="Views/BootstrapScriptTreeElement.js"></script>
     <script src="Views/BoxModelDetailsSectionRow.js"></script>
     <script src="Views/BreakpointActionView.js"></script>
     <script src="Views/BreakpointTreeElement.js"></script>
     <script src="Views/ScriptTimelineDataGrid.js"></script>
     <script src="Views/ScriptTimelineDataGridNode.js"></script>
     <script src="Views/ScriptTimelineOverviewGraph.js"></script>
-    <script src="Views/ScriptTreeElement.js"></script>
     <script src="Views/ScrubberNavigationItem.js"></script>
     <script src="Views/SearchBar.js"></script>
     <script src="Views/SearchResultTreeElement.js"></script>
index 3790110..57da966 100644 (file)
 
 WI.LocalScript = class LocalScript extends WI.Script
 {
-    constructor(target, url, sourceType, text)
+    constructor(target, url, sourceURL, sourceType, source, {injected, editable} = {})
     {
-        super(target, null, WI.TextRange.fromText(text), url, sourceType);
+        const id = null;
+        super(target, id, WI.TextRange.fromText(source), url, sourceType, injected, sourceURL);
 
-        this._text = text;
+        this._editable = !!editable;
+
+        // Finalize WI.SourceCode.
+        const base64Encoded = false;
+        const mimeType = "text/javascript";
+        this._originalRevision = new WI.SourceCodeRevision(this, source, base64Encoded, mimeType);
+        this._currentRevision = this._originalRevision;
     }
 
     // Public
 
+    get editable() { return this._editable; }
+
+    get supportsScriptBlackboxing()
+    {
+        return false;
+    }
+
     requestContentFromBackend()
     {
-        return Promise.resolve({scriptSource: this._text});
+        return Promise.resolve({
+            scriptSource: this._originalRevision.content,
+        });
+    }
+
+    // Protected
+
+    handleCurrentRevisionContentChange()
+    {
+        super.handleCurrentRevisionContentChange();
+
+        this._range = WI.TextRange.fromText(this._currentRevision.content);
     }
 };
index 967b565..93a4170 100644 (file)
@@ -29,7 +29,7 @@ WI.Script = class Script extends WI.SourceCode
     {
         super();
 
-        console.assert(target instanceof WI.Target);
+        console.assert(target instanceof WI.Target || this instanceof WI.LocalScript);
         console.assert(range instanceof WI.TextRange);
 
         this._target = target;
@@ -127,6 +127,11 @@ WI.Script = class Script extends WI.SourceCode
 
     get displayName()
     {
+        if (isWebInspectorBootstrapScript(this._sourceURL || this._url)) {
+            console.assert(WI.NetworkManager.supportsBootstrapScript());
+            return WI.UIString("Inspector Bootstrap Script");
+        }
+
         if (this._url && !this._dynamicallyAddedScriptElement)
             return WI.displayNameForURL(this._url, this.urlComponents);
 
@@ -153,9 +158,13 @@ WI.Script = class Script extends WI.SourceCode
 
     get displayURL()
     {
+        if (isWebInspectorBootstrapScript(this._sourceURL || this._url)) {
+            console.assert(WI.NetworkManager.supportsBootstrapScript());
+            return WI.UIString("Inspector Bootstrap Script");
+        }
+
         const isMultiLine = true;
         const dataURIMaxSize = 64;
-
         if (this._url)
             return WI.truncateURL(this._url, isMultiLine, dataURIMaxSize);
         if (this._sourceURL)
@@ -185,7 +194,7 @@ WI.Script = class Script extends WI.SourceCode
 
     isMainResource()
     {
-        return this._target.mainResource === this;
+        return this._target && this._target.mainResource === this;
     }
 
     requestContentFromBackend()
@@ -250,7 +259,7 @@ WI.Script = class Script extends WI.SourceCode
             return null;
 
         let resolver = WI.networkManager;
-        if (this._target !== WI.mainTarget)
+        if (this._target && this._target !== WI.mainTarget)
             resolver = this._target.resourceCollection;
 
         try {
diff --git a/Source/WebInspectorUI/UserInterface/Views/BootstrapScriptTreeElement.css b/Source/WebInspectorUI/UserInterface/Views/BootstrapScriptTreeElement.css
new file mode 100644 (file)
index 0000000..722bf53
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 Apple 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:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
+ */
+
+.item.script.bootstrap .status > input[type="checkbox"] {
+    margin-top: 2px;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/BootstrapScriptTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/BootstrapScriptTreeElement.js
new file mode 100644 (file)
index 0000000..572f356
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 Apple 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:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
+ */
+
+WI.BootstrapScriptTreeElement = class BootstrapScriptTreeElement extends WI.ScriptTreeElement
+{
+    constructor(bootstrapScript)
+    {
+        console.assert(bootstrapScript === WI.networkManager.bootstrapScript);
+
+        super(bootstrapScript);
+
+        this.addClassName("bootstrap");
+    }
+
+    // Protected
+
+    onattach()
+    {
+        super.onattach();
+
+        WI.NetworkManager.addEventListener(WI.NetworkManager.Event.BootstrapScriptEnabledChanged, this._handleNetworkManagerBootstrapScriptEnabledChanged, this);
+
+        this.status = document.createElement("input");
+        this.status.type = "checkbox";
+        this.status.checked = WI.networkManager.bootstrapScriptEnabled;
+        this.status.addEventListener("change", (event) => {
+            WI.networkManager.bootstrapScriptEnabled = event.target.checked;
+        });
+    }
+
+    ondetach()
+    {
+        WI.NetworkManager.removeEventListener(WI.NetworkManager.Event.BootstrapScriptEnabledChanged, this._handleNetworkManagerBootstrapScriptEnabledChanged, this);
+
+        super.ondetach();
+    }
+
+    ondelete()
+    {
+        WI.networkManager.destroyBootstrapScript();
+
+        return true;
+    }
+
+    onspace()
+    {
+        WI.networkManager.bootstrapScriptEnabled = !WI.networkManager.bootstrapScriptEnabled;
+
+        return true;
+    }
+
+    canSelectOnMouseDown(event)
+    {
+        if (this.status.contains(event.target))
+            return false;
+
+        return super.canSelectOnMouseDown(event);
+    }
+
+    populateContextMenu(contextMenu, event)
+    {
+        let toggleEnabledString = WI.networkManager.bootstrapScriptEnabled ? WI.UIString("Disable Inspector Bootstrap Script") : WI.UIString("Enable Inspector Bootstrap Script");
+        contextMenu.appendItem(toggleEnabledString, () => {
+            WI.networkManager.bootstrapScriptEnabled = !WI.networkManager.bootstrapScriptEnabled;
+        });
+
+        contextMenu.appendItem(WI.UIString("Remove Inspector Bootstrap Script"), () => {
+            WI.networkManager.destroyBootstrapScript();
+        });
+
+        super.populateContextMenu(contextMenu, event);
+    }
+
+    updateStatus()
+    {
+        // Do nothing. Do not allow ScriptTreeElement / SourceCodeTreeElement to modify our status element.
+    }
+
+    // Private
+
+    _handleNetworkManagerBootstrapScriptEnabledChanged(event)
+    {
+        this.status.checked = WI.networkManager.bootstrapScriptEnabled;
+    }
+};
index 846e8da..9f17878 100644 (file)
@@ -23,8 +23,6 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-.item.resource.override .status > div {
-    width: 14px;
-    height: 16px;
-    margin-top: 1px;
+.item.resource.override .status > input[type="checkbox"] {
+    margin-top: 2px;
 }
index 5350317..7127af9 100644 (file)
@@ -159,6 +159,14 @@ WI.OpenResourceDialog = class OpenResourceDialog extends WI.Dialog
 
         this._addLocalResourceOverrides();
 
+        if (WI.NetworkManager.supportsBootstrapScript()) {
+            let bootstrapScript = WI.networkManager.bootstrapScript;
+            if (bootstrapScript) {
+                const suppressFilterUpdate = true;
+                this._addResource(bootstrapScript, suppressFilterUpdate);
+            }
+        }
+
         this._updateFilter();
 
         this._inputElement.focus();
index 725f190..dfc551a 100644 (file)
@@ -77,6 +77,9 @@ WI.ScriptContentView = class ScriptContentView extends WI.ContentView
         this._textEditor.addEventListener(WI.TextEditor.Event.MIMETypeChanged, this._handleTextEditorMIMETypeChanged, this);
         this._textEditor.addEventListener(WI.SourceCodeTextEditor.Event.ContentWillPopulate, this._contentWillPopulate, this);
         this._textEditor.addEventListener(WI.SourceCodeTextEditor.Event.ContentDidPopulate, this._contentDidPopulate, this);
+
+        if (this._script instanceof WI.LocalScript && this._script.editable)
+            this._textEditor.addEventListener(WI.TextEditor.Event.ContentDidChange, this._handleTextEditorContentDidChange, this);
     }
 
     // Public
@@ -211,7 +214,7 @@ WI.ScriptContentView = class ScriptContentView extends WI.ContentView
             return;
 
         // Allow editing any local file since edits can be saved and reloaded right from the Inspector.
-        if (this._script.urlComponents.scheme === "file")
+        if (this._script.urlComponents.scheme === "file" || (this._script instanceof WI.LocalScript && this._script.editable))
             this._textEditor.readOnly = false;
 
         this.element.removeChildren();
@@ -220,15 +223,22 @@ WI.ScriptContentView = class ScriptContentView extends WI.ContentView
 
     _contentDidPopulate(event)
     {
+        let isLocalScript = this._script instanceof WI.LocalScript;
+
         this._prettyPrintButtonNavigationItem.enabled = this._textEditor.canBeFormatted();
 
-        this._showTypesButtonNavigationItem.enabled = this._textEditor.canShowTypeAnnotations();
+        this._showTypesButtonNavigationItem.enabled = !isLocalScript && this._textEditor.canShowTypeAnnotations();
         this._showTypesButtonNavigationItem.activated = WI.settings.showJavaScriptTypeInformation.value;
 
-        this._codeCoverageButtonNavigationItem.enabled = this._textEditor.canShowCoverageHints();
+        this._codeCoverageButtonNavigationItem.enabled = !isLocalScript && this._textEditor.canShowCoverageHints();
         this._codeCoverageButtonNavigationItem.activated = WI.settings.enableControlFlowProfiler.value;
     }
 
+    _handleTextEditorContentDidChange(event)
+    {
+        this._script.currentRevision.updateRevisionContent(this._textEditor.string);
+    }
+
     _togglePrettyPrint(event)
     {
         var activated = !this._prettyPrintButtonNavigationItem.activated;
index 6dae664..33fc9ff 100644 (file)
@@ -36,11 +36,15 @@ WI.ScriptTreeElement = class ScriptTreeElement extends WI.SourceCodeTreeElement
         this.mainTitle = script.displayName;
 
         if (script.url && !script.dynamicallyAddedScriptElement) {
-            // Show the host as the subtitle if it is different from the main title.
-            let host = WI.displayNameForHost(script.urlComponents.host);
-            this.subtitle = this.mainTitle !== host ? host : null;
+            if (script.urlComponents.scheme === "web-inspector")
+                this.tooltip = this.mainTitle;
+            else {
+                // Show the host as the subtitle if it is different from the main title.
+                let host = WI.displayNameForHost(script.urlComponents.host);
+                this.subtitle = this.mainTitle !== host ? host : null;
 
-            this.tooltip = script.url;
+                this.tooltip = script.url;
+            }
 
             this.addClassName(WI.ResourceTreeElement.ResourceIconStyleClassName);
             this.addClassName(WI.Resource.Type.Script);
index a432f55..54fc01c 100644 (file)
@@ -252,6 +252,8 @@ WI.SourceCodeTextEditor = class SourceCodeTextEditor extends WI.TextEditor
             return false;
         if (this._sourceCode instanceof WI.LocalResource)
             return false;
+        if (this._sourceCode instanceof WI.LocalScript)
+            return false;
 
         let caseSensitive = WI.SearchUtilities.defaultSettings.caseSensitive.value;
         let isRegex = WI.SearchUtilities.defaultSettings.regularExpression.value;
@@ -2179,7 +2181,7 @@ WI.SourceCodeTextEditor = class SourceCodeTextEditor extends WI.TextEditor
     _createTypeTokenAnnotator()
     {
         // COMPATIBILITY (iOS 8): Runtime.getRuntimeTypesForVariablesAtOffsets did not exist yet.
-        if (!this.target.hasCommand("Runtime.getRuntimeTypesForVariablesAtOffsets"))
+        if (!this.target || !this.target.hasCommand("Runtime.getRuntimeTypesForVariablesAtOffsets"))
             return;
 
         var script = this._getAssociatedScript();
@@ -2192,7 +2194,7 @@ WI.SourceCodeTextEditor = class SourceCodeTextEditor extends WI.TextEditor
     _createBasicBlockAnnotator()
     {
         // COMPATIBILITY (iOS 8): Runtime.getBasicBlocks did not exist yet.
-        if (!this.target.hasCommand("Runtime.getBasicBlocks"))
+        if (!this.target || !this.target.hasCommand("Runtime.getBasicBlocks"))
             return;
 
         var script = this._getAssociatedScript();
index bea226e..935b20d 100644 (file)
@@ -113,8 +113,9 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         this._pauseReasonGroup = new WI.DetailsSectionGroup([this._pauseReasonTextRow]);
         this._pauseReasonSection = new WI.DetailsSection("paused-reason", WI.UIString("Pause Reason"), [this._pauseReasonGroup], this._pauseReasonLinkContainerElement);
 
-        this._pauseReasonContainer = document.createElement("div");
+        this._pauseReasonContainer = this.contentView.element.appendChild(document.createElement("div"));
         this._pauseReasonContainer.classList.add("pause-reason-container");
+        this._pauseReasonContainer.hidden = true;
         this._pauseReasonContainer.appendChild(this._pauseReasonSection.element);
 
         this._callStackTreeOutline = this.createContentTreeOutline({suppressFiltering: true});
@@ -126,23 +127,11 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         let callStackGroup = new WI.DetailsSectionGroup([callStackRow]);
         this._callStackSection = new WI.DetailsSection("call-stack", WI.UIString("Call Stack"), [callStackGroup]);
 
-        this._callStackContainer = document.createElement("div");
+        this._callStackContainer = this.contentView.element.appendChild(document.createElement("div"));
         this._callStackContainer.classList.add("call-stack-container");
+        this._callStackContainer.hidden = true;
         this._callStackContainer.appendChild(this._callStackSection.element);
 
-        this._localResourceOverridesTreeOutline = this.createContentTreeOutline({suppressFiltering: true});
-        this._localResourceOverridesTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._handleTreeSelectionDidChange, this);
-
-        let localResourceOverridesRow = new WI.DetailsSectionRow;
-        localResourceOverridesRow.element.appendChild(this._localResourceOverridesTreeOutline.element);
-
-        let localResourceOverridesGroup = new WI.DetailsSectionGroup([localResourceOverridesRow]);
-        this._localResourceOverridesSection = new WI.DetailsSection("local-overrides", WI.UIString("Local Overrides"), [localResourceOverridesGroup]);
-
-        this._localResourceOverridesContainer = document.createElement("div");
-        this._localResourceOverridesContainer.classList.add("local-overrides-container");
-        this._localResourceOverridesContainer.appendChild(this._localResourceOverridesSection.element);
-
         this._mainTargetTreeElement = null;
         this._activeCallFrameTreeElement = null;
 
@@ -206,15 +195,26 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         let breakpointsGroup = new WI.DetailsSectionGroup([breakpointsRow]);
         this._breakpointsSection = new WI.DetailsSection("breakpoints", WI.UIString("Breakpoints"), [breakpointsGroup], breakpointNavigationBarWrapper);
 
-        this._breakpointsContainer = document.createElement("div");
-        this._breakpointsContainer.classList.add("breakpoints-container");
-        this._breakpointsContainer.appendChild(this._breakpointsSection.element);
+        let breakpointsContainer = this.contentView.element.appendChild(document.createElement("div"));
+        breakpointsContainer.classList.add("breakpoints-container");
+        breakpointsContainer.appendChild(this._breakpointsSection.element);
+
+        this._localOverridesTreeOutline = this.createContentTreeOutline({suppressFiltering: true});
+        this._localOverridesTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._handleTreeSelectionDidChange, this);
+
+        let localOverridesRow = new WI.DetailsSectionRow;
+        localOverridesRow.element.appendChild(this._localOverridesTreeOutline.element);
 
-        this.contentView.element.insertBefore(this._breakpointsContainer, this.contentView.element.firstChild);
+        let localOverridesGroup = new WI.DetailsSectionGroup([localOverridesRow]);
+        this._localOverridesSection = new WI.DetailsSection("local-overrides", WI.UIString("Local Overrides"), [localOverridesGroup]);
+
+        this._localOverridesContainer = this.contentView.element.appendChild(document.createElement("div"));
+        this._localOverridesContainer.classList.add("local-overrides-container");
+        this._localOverridesContainer.hidden = true;
+        this._localOverridesContainer.appendChild(this._localOverridesSection.element);
 
         this._resourcesNavigationBar = new WI.NavigationBar;
         this.contentView.addSubview(this._resourcesNavigationBar);
-        this.contentView.element.insertBefore(this._resourcesNavigationBar.element, this._breakpointsContainer.nextSibling);
 
         this._resourceGroupingModeScopeBarItems = {};
         let createResourceGroupingModeScopeBarItem = (mode, label) => {
@@ -228,16 +228,15 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         this._resourceGroupingModeScopeBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._handleResourceGroupingModeScopeBarSelectionChanged, this);
         this._resourcesNavigationBar.addNavigationItem(this._resourceGroupingModeScopeBar);
 
-        let resourcesContainer = document.createElement("div");
+        let resourcesContainer = this.contentView.element.appendChild(document.createElement("div"));
         resourcesContainer.classList.add("resources-container");
-        this.contentView.element.insertBefore(resourcesContainer, this._resourcesNavigationBar.element.nextSibling);
 
         this._resourcesTreeOutline = this.contentTreeOutline;
         this._resourcesTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._handleTreeSelectionDidChange, this);
         this._resourcesTreeOutline.includeSourceMapResourceChildren = true;
         resourcesContainer.appendChild(this._resourcesTreeOutline.element);
 
-        if (InspectorBackend.hasDomain("CSS")) {
+        if (WI.NetworkManager.supportsBootstrapScript() || InspectorBackend.hasDomain("CSS")) {
             let createResourceNavigationBar = new WI.NavigationBar;
 
             let createResourceButtonNavigationItem = new WI.ButtonNavigationItem("create-resource", WI.UIString("Create Resource"), "Images/Plus15.svg", 15, 15);
@@ -272,6 +271,11 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
 
         WI.networkManager.addEventListener(WI.NetworkManager.Event.FrameWasAdded, this._handleFrameWasAdded, this);
 
+        if (WI.NetworkManager.supportsBootstrapScript()) {
+            WI.networkManager.addEventListener(WI.NetworkManager.Event.BootstrapScriptCreated, this._handleBootstrapScriptCreated, this);
+            WI.networkManager.addEventListener(WI.NetworkManager.Event.BootstrapScriptDestroyed, this._handleBootstrapScriptDestroyed, this);
+        }
+
         if (WI.NetworkManager.supportsLocalResourceOverrides()) {
             WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideAdded, this._handleLocalResourceOverrideAdded, this);
             WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideRemoved, this._handleLocalResourceOverrideRemoved, this);
@@ -342,9 +346,15 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
 
         this._handleResourceGroupingModeChanged();
 
+        if (WI.NetworkManager.supportsBootstrapScript()) {
+            let bootstrapScript = WI.networkManager.bootstrapScript;
+            if (bootstrapScript)
+                this._addLocalOverride(bootstrapScript);
+        }
+
         if (WI.NetworkManager.supportsLocalResourceOverrides()) {
             for (let localResourceOverride of WI.networkManager.localResourceOverrides)
-                this._addLocalResourceOverride(localResourceOverride);
+                this._addLocalOverride(localResourceOverride);
         }
 
         if (WI.domDebuggerManager.supported) {
@@ -443,13 +453,16 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         // A custom implementation is needed for this since the frames are populated lazily.
 
         if (representedObject instanceof WI.LocalResourceOverride)
-            return this._localResourceOverridesTreeOutline.findTreeElement(representedObject);
+            return this._localOverridesTreeOutline.findTreeElement(representedObject);
 
         if (representedObject instanceof WI.LocalResource) {
             let localResourceOverride = WI.networkManager.localResourceOverrideForURL(representedObject.url);
-            return this._localResourceOverridesTreeOutline.findTreeElement(localResourceOverride);
+            return this._localOverridesTreeOutline.findTreeElement(localResourceOverride);
         }
 
+        if (representedObject instanceof WI.Script && representedObject === WI.networkManager.bootstrapScript)
+            return this._localOverridesTreeOutline.findTreeElement(representedObject);
+
         if (!this._mainFrameTreeElement && (representedObject instanceof WI.Resource || representedObject instanceof WI.Frame || representedObject instanceof WI.Collection)) {
             // All resources are under the main frame, so we need to return early if we don't have the main frame yet.
             return null;
@@ -545,7 +558,7 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
 
         treeOutline.addEventListener(WI.TreeOutline.Event.ElementRevealed, (event) => {
             let treeElement = event.data.element;
-            let detailsSections = [this._pauseReasonSection, this._callStackSection, this._breakpointsSection, this._localResourceOverridesSection];
+            let detailsSections = [this._pauseReasonSection, this._callStackSection, this._breakpointsSection, this._localOverridesSection];
             let detailsSection = detailsSections.find((detailsSection) => detailsSection.element.contains(treeElement.listItemElement));
             if (!detailsSection)
                 return;
@@ -713,6 +726,8 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
     _compareTreeElements(a, b)
     {
         const rankFunctions = [
+            (treeElement) => treeElement instanceof WI.ScriptTreeElement && treeElement.representedObject === WI.networkManager.bootstrapScript,
+            (treeElement) => treeElement instanceof WI.LocalResourceOverrideTreeElement,
             (treeElement) => treeElement instanceof WI.CSSStyleSheetTreeElement && treeElement.representedObject.isInspectorStyleSheet(),
             (treeElement) => treeElement === this._mainFrameTreeElement,
             (treeElement) => treeElement instanceof WI.FrameTreeElement,
@@ -1235,37 +1250,43 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
             this._addIssue(issue, sourceCode);
     }
 
-    _addLocalResourceOverride(localResourceOverride)
+    _addLocalOverride(localOverride)
     {
-        console.assert(WI.NetworkManager.supportsLocalResourceOverrides());
+        console.assert(WI.NetworkManager.supportsBootstrapScript() || WI.NetworkManager.supportsLocalResourceOverrides());
 
-        if (this._localResourceOverridesTreeOutline.findTreeElement(localResourceOverride))
+        if (this._localOverridesTreeOutline.findTreeElement(localOverride))
             return;
 
-        let parentTreeElement = this._localResourceOverridesTreeOutline;
-        let resourceTreeElement = new WI.LocalResourceOverrideTreeElement(localResourceOverride.localResource, localResourceOverride);
-        let index = insertionIndexForObjectInListSortedByFunction(resourceTreeElement, parentTreeElement.children, this._boundCompareTreeElements);
-        parentTreeElement.insertChild(resourceTreeElement, index);
+        let parentTreeElement = this._localOverridesTreeOutline;
+
+        let localOverrideTreeElement = null;
+        if (localOverride === WI.networkManager.bootstrapScript)
+            localOverrideTreeElement = new WI.BootstrapScriptTreeElement(localOverride);
+        else if (localOverride instanceof WI.LocalResourceOverride)
+            localOverrideTreeElement = new WI.LocalResourceOverrideTreeElement(localOverride.localResource, localOverride);
+        console.assert(localOverrideTreeElement);
+
+        let index = insertionIndexForObjectInListSortedByFunction(localOverrideTreeElement, parentTreeElement.children, this._boundCompareTreeElements);
+        parentTreeElement.insertChild(localOverrideTreeElement, index);
 
-        if (!this._localResourceOverridesContainer.parentNode)
-            this.contentView.element.insertBefore(this._localResourceOverridesContainer, this._resourcesNavigationBar.element);
+        this._localOverridesContainer.hidden = false;
     }
 
-    _removeLocalResourceOverride(localResourceOverride)
+    _removeResourceOverride(localOverride)
     {
-        console.assert(WI.NetworkManager.supportsLocalResourceOverrides());
+        console.assert(WI.NetworkManager.supportsBootstrapScript() || WI.NetworkManager.supportsLocalResourceOverrides());
 
-        let resourceTreeElement = this._localResourceOverridesTreeOutline.findTreeElement(localResourceOverride);
+        let resourceTreeElement = this._localOverridesTreeOutline.findTreeElement(localOverride);
         if (!resourceTreeElement)
             return;
 
         let wasSelected = resourceTreeElement.selected;
 
-        let parentTreeElement = this._localResourceOverridesTreeOutline;
+        let parentTreeElement = this._localOverridesTreeOutline;
         parentTreeElement.removeChild(resourceTreeElement);
 
         if (!parentTreeElement.children.length) {
-            this._localResourceOverridesContainer.remove();
+            this._localOverridesContainer.hidden = true;
 
             if (wasSelected && WI.networkManager.mainFrame && WI.networkManager.mainFrame.mainResource)
                 WI.showRepresentedObject(WI.networkManager.mainFrame.mainResource);
@@ -1887,6 +1908,14 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
 
     _populateCreateResourceContextMenu(contextMenu)
     {
+        if (WI.NetworkManager.supportsBootstrapScript()) {
+            contextMenu.appendItem(WI.UIString("Inspector Bootstrap Script"), async () => {
+                await WI.networkManager.createBootstrapScript();
+                WI.networkManager.bootstrapScriptEnabled = true;
+                WI.showRepresentedObject(WI.networkManager.bootstrapScript);
+            });
+        }
+
         if (InspectorBackend.hasDomain("CSS")) {
             let addInspectorStyleSheetItem = (menu, frame) => {
                 menu.appendItem(WI.UIString("Inspector Style Sheet"), () => {
@@ -1990,14 +2019,24 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         this._addResourcesRecursivelyForFrame(frame);
     }
 
+    _handleBootstrapScriptCreated(event)
+    {
+        this._addLocalOverride(event.data.bootstrapScript);
+    }
+
+    _handleBootstrapScriptDestroyed(event)
+    {
+        this._removeResourceOverride(event.data.bootstrapScript);
+    }
+
     _handleLocalResourceOverrideAdded(event)
     {
-        this._addLocalResourceOverride(event.data.localResourceOverride);
+        this._addLocalOverride(event.data.localResourceOverride);
     }
 
     _handleLocalResourceOverrideRemoved(event)
     {
-        this._removeLocalResourceOverride(event.data.localResourceOverride);
+        this._removeResourceOverride(event.data.localResourceOverride);
     }
 
     _handleDebuggerBreakpointAdded(event)
@@ -2088,10 +2127,10 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
 
     _handleDebuggerPaused(event)
     {
-        this.contentView.element.insertBefore(this._callStackContainer, this.contentView.element.firstChild);
+        this._callStackContainer.hidden = false;
 
         if (this._updatePauseReason())
-            this.contentView.element.insertBefore(this._pauseReasonContainer, this.contentView.element.firstChild);
+            this._pauseReasonContainer.hidden = false;
 
         this._debuggerPauseResumeButtonItem.enabled = true;
         this._debuggerPauseResumeButtonItem.toggled = true;
@@ -2104,9 +2143,9 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
 
     _handleDebuggerResumed(event)
     {
-        this._callStackContainer.remove();
+        this._callStackContainer.hidden = true;
 
-        this._pauseReasonContainer.remove();
+        this._pauseReasonContainer.hidden = true;
 
         this._debuggerPauseResumeButtonItem.enabled = true;
         this._debuggerPauseResumeButtonItem.toggled = false;