Can't use $0, $1 etc when inspecting Google Docs pages because the content uses these...
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 6 Aug 2019 03:37:44 +0000 (03:37 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 6 Aug 2019 03:37:44 +0000 (03:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195834

Reviewed by Joseph Pecoraro.

Allow the user to alias saved results by providing a different prefix (e.g. "$") from within
Web Inspector. When changing the alias, all existing saved results will update to be
reference-able from the new alias.

Source/JavaScriptCore:

* inspector/protocol/Runtime.json:
Add `setSavedResultAlias` command.

* inspector/agents/InspectorRuntimeAgent.h:
* inspector/agents/InspectorRuntimeAgent.cpp:
(Inspector::InspectorRuntimeAgent::setSavedResultAlias): Added.

* inspector/InjectedScriptHost.h:
(Inspector::InjectedScriptHost::setSavedResultAlias): Added.
(Inspector::InjectedScriptHost::savedResultAlias const): Added.
* inspector/JSInjectedScriptHost.h:
* inspector/JSInjectedScriptHost.cpp:
(Inspector::JSInjectedScriptHost::savedResultAlias const): Added.
* inspector/JSInjectedScriptHostPrototype.cpp:
(Inspector::JSInjectedScriptHostPrototype::finishCreation):
(Inspector::jsInjectedScriptHostPrototypeAttributeSavedResultAlias): Added.
Store the saved result alias on the `InjectedScriptHost` since it is a shared object among
all `InjectedScript`.

* inspector/InjectedScriptSource.js:
(BasicCommandLineAPI):

Source/WebCore:

Test: inspector/runtime/setSavedResultAlias.html

* inspector/CommandLineAPIModuleSource.js:
(CommandLineAPI):

Source/WebInspectorUI:

* UserInterface/Controllers/RuntimeManager.js:
(WI.RuntimeManager):
(WI.RuntimeManager.preferredSavedResultPrefix): Added.
(WI.RuntimeManager.prototype.initializeTarget):
* UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:
(WI.JavaScriptRuntimeCompletionProvider.completionControllerCompletionsNeeded.receivedPropertyNames):

* UserInterface/Base/Setting.js:
* UserInterface/Base/Main.js:
(WI.contentLoaded):
(WI.contentLoaded.updateConsoleSavedResultPrefixCSSVariable): Added.
* UserInterface/Views/ConsoleMessageView.js:
(WI.ConsoleMessageView.prototype.toClipboardString):
(WI.ConsoleMessageView.prototype.removeEventListeners): Added.
(WI.ConsoleMessageView.prototype._appendSavedResultIndex):
(WI.ConsoleMessageView.prototype._appendSavedResultIndex.updateSavedVariableText): Added.
(WI.ConsoleMessageView.prototype._rootPropertyPathForObject):
(WI.ConsoleMessageView.prototype._rootPropertyPathForObject.prefixSavedResultIndex): Added.
* UserInterface/Views/LogContentView.js:
(WI.LogContentView.prototype._sessionStarted):
(WI.LogContentView.prototype._logCleared):
* UserInterface/Views/DOMTreeOutline.css:
(.tree-outline.dom.show-last-selected li.last-selected > span::after):
* UserInterface/Views/QuickConsole.js:
(WI.QuickConsole):
(WI.QuickConsole.prototype.closed):
(WI.QuickConsole.prototype._updateAutomaticExecutionContextPathComponentTooltip): Added.
Listen for changes to the setting that holds the current saved result alias and update any
related UI accordingly.

* UserInterface/Views/SettingsTabContentView.js:
(WI.SettingsTabContentView.prototype._createConsoleSettingsView):
* UserInterface/Views/SettingsTabContentView.css:
(.content-view.settings > .settings-view > .container > .editor-group > .editor input[type="text"]): Added.
* UserInterface/Views/SettingsGroup.js:
(WI.SettingsGroup.prototype.addCustomEditor): Added.
Add an input to the Settings tab that controls the saved result prefix alias. Only allow
[a-zA-Z0-9_$] as values (but [0-9] cannot be used as the start).

* UserInterface/Models/PropertyPath.js:
(WI.PropertyPath.prototype.set pathComponent): Added.
Miscellaneous getters/setters.

* Localizations/en.lproj/localizedStrings.js:

* UserInterface/Test/TestHarness.js:
(TestHarness.prototype.newline): Added.
Convenience function for adding newlines to test results.

LayoutTests:

* inspector/runtime/setSavedResultAlias.html: Added.
* inspector/runtime/setSavedResultAlias-expected.txt: Added.
* http/tests/inspector/dom/cross-domain-inspected-node-access-expected.txt:
* inspector/console/command-line-api-expected.txt:
* inspector/console/command-line-api-exception.html:
* inspector/console/command-line-api-exception-expected.txt:
* inspector/console/command-line-api-exception-nested-catch.html:
* inspector/console/command-line-api-exception-nested-catch-expected.txt:

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

36 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/inspector/dom/cross-domain-inspected-node-access-expected.txt
LayoutTests/inspector/console/command-line-api-expected.txt
LayoutTests/inspector/debugger/command-line-api-exception-expected.txt
LayoutTests/inspector/debugger/command-line-api-exception-nested-catch-expected.txt
LayoutTests/inspector/debugger/command-line-api-exception-nested-catch.html
LayoutTests/inspector/debugger/command-line-api-exception.html
LayoutTests/inspector/runtime/setSavedResultAlias-expected.txt [new file with mode: 0644]
LayoutTests/inspector/runtime/setSavedResultAlias.html [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/InjectedScriptHost.h
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/inspector/JSInjectedScriptHost.cpp
Source/JavaScriptCore/inspector/JSInjectedScriptHost.h
Source/JavaScriptCore/inspector/JSInjectedScriptHostPrototype.cpp
Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.h
Source/JavaScriptCore/inspector/protocol/Runtime.json
Source/WebCore/ChangeLog
Source/WebCore/inspector/CommandLineAPIModuleSource.js
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Main.js
Source/WebInspectorUI/UserInterface/Base/Setting.js
Source/WebInspectorUI/UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js
Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js
Source/WebInspectorUI/UserInterface/Models/PropertyPath.js
Source/WebInspectorUI/UserInterface/Test/TestHarness.js
Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.js
Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.css
Source/WebInspectorUI/UserInterface/Views/LogContentView.js
Source/WebInspectorUI/UserInterface/Views/QuickConsole.js
Source/WebInspectorUI/UserInterface/Views/SettingsGroup.js
Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.css
Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
Source/WebInspectorUI/UserInterface/Views/SettingsView.js

index de4fc45..d4be7c0 100644 (file)
@@ -1,5 +1,25 @@
 2019-08-05  Devin Rousso  <drousso@apple.com>
 
+        Can't use $0, $1 etc when inspecting Google Docs pages because the content uses these for function names
+        https://bugs.webkit.org/show_bug.cgi?id=195834
+
+        Reviewed by Joseph Pecoraro.
+
+        Allow the user to alias saved results by providing a different prefix (e.g. "$") from within
+        Web Inspector. When changing the alias, all existing saved results will update to be
+        reference-able from the new alias.
+
+        * inspector/runtime/setSavedResultAlias.html: Added.
+        * inspector/runtime/setSavedResultAlias-expected.txt: Added.
+        * http/tests/inspector/dom/cross-domain-inspected-node-access-expected.txt:
+        * inspector/console/command-line-api-expected.txt:
+        * inspector/console/command-line-api-exception.html:
+        * inspector/console/command-line-api-exception-expected.txt:
+        * inspector/console/command-line-api-exception-nested-catch.html:
+        * inspector/console/command-line-api-exception-nested-catch-expected.txt:
+
+2019-08-05  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Timelines: disable related agents when the tab is closed
         https://bugs.webkit.org/show_bug.cgi?id=200118
 
index 9f857e9..4962a3f 100644 (file)
@@ -1,5 +1,5 @@
-CONSOLE MESSAGE: line 44: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
-CONSOLE MESSAGE: line 44: Blocked a frame with origin "http://localhost:8000" from accessing a frame with origin "http://127.0.0.1:8000". Protocols, domains, and ports must match.
+CONSOLE MESSAGE: line 49: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+CONSOLE MESSAGE: line 49: Blocked a frame with origin "http://localhost:8000" from accessing a frame with origin "http://127.0.0.1:8000". Protocols, domains, and ports must match.
 Test that code evaluated in the main frame cannot access $0 that resolves to a node in a frame from a different domain. Bug 105423.
 
 
index d3365b5..43e0dea 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 12: The console function $() has changed from $=getElementById(id) to $=querySelector(selector). You might try $("#%s")
+CONSOLE MESSAGE: line 17: The console function $() has changed from $=getElementById(id) to $=querySelector(selector). You might try $("#%s")
 Tests that command line api works.
 
 
index 7075e97..2b798e3 100644 (file)
@@ -15,7 +15,8 @@ Checks that $exception is available and accurate in evaluations when paused on a
 
 == Running test suite: CommandLineAPI.$exception
 -- Running test case: BeforeExceptions
-PASS: $exception should be undefined if there is no exception.
+PASS: $exception should throw an error if there is no exception.
+ReferenceError: Can't find variable: $exception
 
 -- Running test case: UncaughtTypeException
 $exception => TypeError: undefined is not an object (evaluating '[].x.x')
@@ -69,5 +70,6 @@ PASS: `$exception` should be equal to `e`.
 $exception => Object
 
 -- Running test case: AfterExceptions
-PASS: $exception should be undefined if there is no exception.
+PASS: $exception should throw an error if there is no exception.
+ReferenceError: Can't find variable: $exception
 
index 0cb99ba..7e317ac 100644 (file)
@@ -5,7 +5,8 @@ Checks that $exception is the value of the current exception, even in nested cat
 
 == Running test suite: CommandLineAPI.$exception
 -- Running test case: EmptyBefore
-PASS: $exception should be undefined if there is no exception.
+PASS: $exception should throw an error if there is no exception.
+ReferenceError: Can't find variable: $exception
 
 -- Running test case: CheckExceptionInsideNestedCatchBlocks
 OUTER 1: $exception => outer exception
@@ -16,5 +17,6 @@ OUTER 2: $exception => outer exception
   CATCH: $exception === e1 ? true
 
 -- Running test case: EmptyAfter
-PASS: $exception should be undefined if there is no exception.
+PASS: $exception should throw an error if there is no exception.
+ReferenceError: Can't find variable: $exception
 
index 53e6372..7e9166d 100644 (file)
@@ -27,10 +27,11 @@ function test()
 
     suite.addTestCase({
         name: "EmptyBefore",
-        description: "Without exceptions, $exception should be undefined",
+        description: "Without exceptions, $exception should not be defined",
         test(resolve, reject) {
             WI.runtimeManager.evaluateInInspectedWindow("$exception", {objectGroup: "test", includeCommandLineAPI: true, doNotPauseOnExceptionsAndMuteConsole: true}, (result, wasThrown) => {
-                InspectorTest.expectThat(result.description === "undefined", "$exception should be undefined if there is no exception.");
+                InspectorTest.expectThat(wasThrown, "$exception should throw an error if there is no exception.");
+                InspectorTest.log(result.description);
                 resolve();
             });
         }
@@ -87,10 +88,11 @@ function test()
 
     suite.addTestCase({
         name: "EmptyAfter",
-        description: "Without exceptions, $exception should be undefined",
+        description: "Without exceptions, $exception should not be defined",
         test(resolve, reject) {
             WI.runtimeManager.evaluateInInspectedWindow("$exception", {objectGroup: "test", includeCommandLineAPI: true, doNotPauseOnExceptionsAndMuteConsole: true}, (result, wasThrown) => {
-                InspectorTest.expectThat(result.description === "undefined", "$exception should be undefined if there is no exception.");
+                InspectorTest.expectThat(wasThrown, "$exception should throw an error if there is no exception.");
+                InspectorTest.log(result.description);
                 resolve();
             });
         }
index d982daa..43ed6db 100644 (file)
@@ -18,7 +18,8 @@ function test()
             name, description: "Without exceptions, $exception should be undefined",
             test(resolve, reject) {
                 WI.runtimeManager.evaluateInInspectedWindow("$exception", {objectGroup: "test", includeCommandLineAPI: true, doNotPauseOnExceptionsAndMuteConsole: true}, (result, wasThrown) => {
-                    InspectorTest.expectThat(result.description === "undefined", "$exception should be undefined if there is no exception.");
+                    InspectorTest.expectThat(wasThrown, "$exception should throw an error if there is no exception.");
+                    InspectorTest.log(result.description);
                     resolve();
                 });
             }
diff --git a/LayoutTests/inspector/runtime/setSavedResultAlias-expected.txt b/LayoutTests/inspector/runtime/setSavedResultAlias-expected.txt
new file mode 100644 (file)
index 0000000..bca810f
--- /dev/null
@@ -0,0 +1,28 @@
+Tests for the Runtime.setSavedResultAlias command.
+
+
+== Running test suite: Runtime.setSavedResultAlias
+-- Running test case: Runtime.setSavedResultAlias
+Saving value...
+
+Getting saved value at '$1'...
+PASS: Saved value should match $1.
+Getting saved value at 'temp1'...
+PASS: Should produce an exception.
+Error: ReferenceError: Can't find variable: temp1
+
+Changing saved result alias to "temp"...
+
+Getting saved value at '$1'...
+PASS: Saved value should still match $1.
+Getting saved value at 'temp1'...
+PASS: Saved value should now match temp1.
+
+Changing saved result alias to ""...
+
+Getting saved value at '$1'...
+PASS: Saved value should still match $1.
+Getting saved value at 'temp1'...
+PASS: Should produce an exception.
+Error: ReferenceError: Can't find variable: temp1
+
diff --git a/LayoutTests/inspector/runtime/setSavedResultAlias.html b/LayoutTests/inspector/runtime/setSavedResultAlias.html
new file mode 100644 (file)
index 0000000..6c960dc
--- /dev/null
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    function createCallArgumentWithValue(value) {
+        return {value};
+    }
+
+    async function getSavedResult(savedResultPrefix, savedResultIndex) {
+        InspectorTest.log(`Getting saved value at '${savedResultPrefix}${savedResultIndex}'...`);
+        let {result, wasThrown} = await RuntimeAgent.evaluate.invoke({
+            expression: savedResultPrefix + savedResultIndex,
+            objectGroup: "test",
+            includeCommandLineAPI: true,
+            returnByValue: true,
+        });
+        if (wasThrown)
+            throw new Error(result.description);
+        return result.value;
+    }
+
+    async function changeSavedResultAlias(alias) {
+        InspectorTest.log(`Changing saved result alias to "${alias}"...`);
+        await RuntimeAgent.setSavedResultAlias(alias);
+    }
+
+    let suite = InspectorTest.createAsyncSuite("Runtime.setSavedResultAlias");
+
+    suite.addTestCase({
+        name: "Runtime.setSavedResultAlias",
+        description: "Saving a new value should produce a new $n value.",
+        async test() {
+            const value = 123;
+
+            InspectorTest.log("Saving value...");
+            let {savedResultIndex} = await RuntimeAgent.saveResult(createCallArgumentWithValue(value));
+
+            InspectorTest.newline();
+
+            InspectorTest.expectEqual(value, await getSavedResult("$", savedResultIndex), `Saved value should match $${savedResultIndex}.`);
+            await InspectorTest.expectException(() => getSavedResult("temp", savedResultIndex));
+
+            InspectorTest.newline();
+
+            await changeSavedResultAlias("temp");
+
+            InspectorTest.newline();
+
+            InspectorTest.expectEqual(value, await getSavedResult("$", savedResultIndex), `Saved value should still match $${savedResultIndex}.`);
+            InspectorTest.expectEqual(value, await getSavedResult("temp", savedResultIndex), `Saved value should now match temp${savedResultIndex}.`);
+
+            InspectorTest.newline();
+
+            await changeSavedResultAlias("");
+
+            InspectorTest.newline();
+
+            InspectorTest.expectEqual(value, await getSavedResult("$", savedResultIndex), `Saved value should still match $${savedResultIndex}.`);
+            await InspectorTest.expectException(() => getSavedResult("temp", savedResultIndex));
+        },
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Tests for the Runtime.setSavedResultAlias command.</p>
+</body>
+</html>
index be7c421..007e383 100644 (file)
@@ -1,5 +1,38 @@
 2019-08-05  Devin Rousso  <drousso@apple.com>
 
+        Can't use $0, $1 etc when inspecting Google Docs pages because the content uses these for function names
+        https://bugs.webkit.org/show_bug.cgi?id=195834
+
+        Reviewed by Joseph Pecoraro.
+
+        Allow the user to alias saved results by providing a different prefix (e.g. "$") from within
+        Web Inspector. When changing the alias, all existing saved results will update to be
+        reference-able from the new alias.
+
+        * inspector/protocol/Runtime.json:
+        Add `setSavedResultAlias` command.
+
+        * inspector/agents/InspectorRuntimeAgent.h:
+        * inspector/agents/InspectorRuntimeAgent.cpp:
+        (Inspector::InspectorRuntimeAgent::setSavedResultAlias): Added.
+
+        * inspector/InjectedScriptHost.h:
+        (Inspector::InjectedScriptHost::setSavedResultAlias): Added.
+        (Inspector::InjectedScriptHost::savedResultAlias const): Added.
+        * inspector/JSInjectedScriptHost.h:
+        * inspector/JSInjectedScriptHost.cpp:
+        (Inspector::JSInjectedScriptHost::savedResultAlias const): Added.
+        * inspector/JSInjectedScriptHostPrototype.cpp:
+        (Inspector::JSInjectedScriptHostPrototype::finishCreation):
+        (Inspector::jsInjectedScriptHostPrototypeAttributeSavedResultAlias): Added.
+        Store the saved result alias on the `InjectedScriptHost` since it is a shared object among
+        all `InjectedScript`.
+
+        * inspector/InjectedScriptSource.js:
+        (BasicCommandLineAPI):
+
+2019-08-05  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Timelines: disable related agents when the tab is closed
         https://bugs.webkit.org/show_bug.cgi?id=200118
 
index 2784036..8f7d374 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "JSCJSValueInlines.h"
 #include "PerGlobalObjectWrapperWorld.h"
+#include <wtf/Optional.h>
 #include <wtf/RefCounted.h>
 
 namespace Inspector {
@@ -43,8 +44,12 @@ public:
     JSC::JSValue wrapper(JSC::ExecState*, JSC::JSGlobalObject*);
     void clearAllWrappers();
 
+    void setSavedResultAlias(const Optional<String>& alias) { m_savedResultAlias = alias; }
+    const Optional<String>& savedResultAlias() const { return m_savedResultAlias; }
+
 private:
     PerGlobalObjectWrapperWorld m_wrappers;
+    Optional<String> m_savedResultAlias;
 };
 
 } // namespace Inspector
index 630e5dc..0582e3b 100644 (file)
@@ -1453,22 +1453,37 @@ function bind(func, thisObject, ...outerArgs)
 
 function BasicCommandLineAPI(callFrame)
 {
-    this.$_ = injectedScript._lastResult;
-    this.$exception = injectedScript._exceptionValue;
+    let savedResultAlias = InjectedScriptHost.savedResultAlias;
+
+    let defineGetter = (key, value) => {
+        if (typeof value !== "function") {
+            let originalValue = value;
+            value = function() { return originalValue; };
+        }
+
+        this.__defineGetter__("$" + key, value);
+        if (savedResultAlias)
+            this.__defineGetter__(savedResultAlias + key, value);
+    };
+
+    if ("_lastResult" in injectedScript)
+        defineGetter("_", injectedScript._lastResult);
+
+    if ("_exceptionValue" in injectedScript)
+        defineGetter("exception", injectedScript._exceptionValue);
 
     if ("_eventValue" in injectedScript)
-        this.$event = injectedScript._eventValue;
-    else if ("$event" in this)
-        delete this.$event;
+        defineGetter("event", injectedScript._eventValue);
 
     // $1-$99
     for (let i = 1; i <= injectedScript._savedResults.length; ++i)
-        this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i));
+        defineGetter(i, bind(injectedScript._savedResult, injectedScript, i));
 
     // Command Line API methods.
     for (let i = 0; i < BasicCommandLineAPI.methods.length; ++i) {
         let method = BasicCommandLineAPI.methods[i];
-        this[method.name] = method;
+        this[method] = bind(commandLineAPIImpl[method], commandLineAPIImpl);
+        this[method].toString = function() { return "function " + method + "() { [Command Line API] }" };
     }
 }
 
index d9725c0..9b7a22e 100644 (file)
@@ -102,6 +102,14 @@ JSValue JSInjectedScriptHost::evaluate(ExecState* exec) const
     return globalObject->evalFunction();
 }
 
+JSValue JSInjectedScriptHost::savedResultAlias(ExecState* exec) const
+{
+    auto savedResultAlias = impl().savedResultAlias();
+    if (!savedResultAlias)
+        return jsUndefined();
+    return jsString(exec, savedResultAlias.value());
+}
+
 JSValue JSInjectedScriptHost::evaluateWithScopeExtension(ExecState* exec)
 {
     VM& vm = exec->vm();
index 8d920f5..d9978c7 100644 (file)
@@ -57,6 +57,7 @@ public:
 
     // Attributes.
     JSC::JSValue evaluate(JSC::ExecState*) const;
+    JSC::JSValue savedResultAlias(JSC::ExecState*) const;
 
     // Functions.
     JSC::JSValue evaluateWithScopeExtension(JSC::ExecState*);
index f7553b6..24f01f7 100644 (file)
@@ -54,6 +54,7 @@ static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionQueryOb
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionEvaluateWithScopeExtension(ExecState*);
 
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeAttributeEvaluate(ExecState*);
+static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeAttributeSavedResultAlias(ExecState*);
 
 const ClassInfo JSInjectedScriptHostPrototype::s_info = { "InjectedScriptHost", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSInjectedScriptHostPrototype) };
 
@@ -78,6 +79,7 @@ void JSInjectedScriptHostPrototype::finishCreation(VM& vm, JSGlobalObject* globa
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("evaluateWithScopeExtension", jsInjectedScriptHostPrototypeFunctionEvaluateWithScopeExtension, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
 
     JSC_NATIVE_GETTER_WITHOUT_TRANSITION("evaluate", jsInjectedScriptHostPrototypeAttributeEvaluate, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
+    JSC_NATIVE_GETTER_WITHOUT_TRANSITION("savedResultAlias", jsInjectedScriptHostPrototypeAttributeSavedResultAlias, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
 }
 
 EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeAttributeEvaluate(ExecState* exec)
@@ -93,6 +95,19 @@ EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeAttributeEvaluate(Exec
     return JSValue::encode(castedThis->evaluate(exec));
 }
 
+EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeAttributeSavedResultAlias(ExecState* exec)
+{
+    VM& vm = exec->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    JSValue thisValue = exec->thisValue();
+    JSInjectedScriptHost* castedThis = jsDynamicCast<JSInjectedScriptHost*>(vm, thisValue);
+    if (!castedThis)
+        return throwVMTypeError(exec, scope);
+
+    return JSValue::encode(castedThis->savedResultAlias(exec));
+}
+
 EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionInternalConstructorName(ExecState* exec)
 {
     VM& vm = exec->vm();
index 1f01b10..459d4a4 100644 (file)
@@ -260,6 +260,14 @@ void InspectorRuntimeAgent::saveResult(ErrorString& errorString, const JSON::Obj
     injectedScript.saveResult(errorString, callArgument.toJSONString(), savedResultIndex);
 }
 
+void InspectorRuntimeAgent::setSavedResultAlias(ErrorString&, const String* alias)
+{
+    Optional<String> savedResultAlias;
+    if (alias && !alias->isEmpty())
+        savedResultAlias = *alias;
+    m_injectedScriptManager.injectedScriptHost().setSavedResultAlias(savedResultAlias);
+}
+
 void InspectorRuntimeAgent::releaseObject(ErrorString&, const String& objectId)
 {
     InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
index 038f616..16a7d9f 100644 (file)
@@ -68,6 +68,7 @@ public:
     void getDisplayableProperties(ErrorString&, const String& objectId, const bool* generatePreview, RefPtr<JSON::ArrayOf<Protocol::Runtime::PropertyDescriptor>>& result, RefPtr<JSON::ArrayOf<Protocol::Runtime::InternalPropertyDescriptor>>& internalProperties) final;
     void getCollectionEntries(ErrorString&, const String& objectId, const String* objectGroup, const int* startIndex, const int* numberToFetch, RefPtr<JSON::ArrayOf<Protocol::Runtime::CollectionEntry>>& entries) final;
     void saveResult(ErrorString&, const JSON::Object& callArgument, const int* executionContextId, Optional<int>& savedResultIndex) final;
+    void setSavedResultAlias(ErrorString&, const String* alias) final;
     void releaseObjectGroup(ErrorString&, const String& objectGroup) final;
     void getRuntimeTypesForVariablesAtOffsets(ErrorString&, const JSON::Array& locations, RefPtr<JSON::ArrayOf<Protocol::Runtime::TypeDescription>>&) override;
     void enableTypeProfiler(ErrorString&) override;
index 6902b5e..a7e4391 100644 (file)
             ]
         },
         {
+            "name": "setSavedResultAlias",
+            "description": "Creates an additional reference to all saved values in the Console using the the given string as a prefix instead of $.",
+            "parameters": [
+                { "name": "alias", "type": "string", "optional": true, "description": "Passing an empty/null string will clear the alias." }
+            ]
+        },
+        {
             "name": "releaseObject",
             "description": "Releases remote object with given id.",
             "parameters": [
index 4adc057..c8181c4 100644 (file)
@@ -1,5 +1,21 @@
 2019-08-05  Devin Rousso  <drousso@apple.com>
 
+        Can't use $0, $1 etc when inspecting Google Docs pages because the content uses these for function names
+        https://bugs.webkit.org/show_bug.cgi?id=195834
+
+        Reviewed by Joseph Pecoraro.
+
+        Allow the user to alias saved results by providing a different prefix (e.g. "$") from within
+        Web Inspector. When changing the alias, all existing saved results will update to be
+        reference-able from the new alias.
+
+        Test: inspector/runtime/setSavedResultAlias.html
+
+        * inspector/CommandLineAPIModuleSource.js:
+        (CommandLineAPI):
+
+2019-08-05  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Timelines: disable related agents when the tab is closed
         https://bugs.webkit.org/show_bug.cgi?id=200118
 
index a1f7d57..dfa267d 100644 (file)
@@ -46,16 +46,34 @@ function bind(func, thisObject, ...outerArgs)
  */
 function CommandLineAPI(commandLineAPIImpl, callFrame)
 {
-    this.$_ = injectedScript._lastResult;
-    this.$event = injectedScript._eventValue;
-    this.$exception = injectedScript._exceptionValue;
+    let savedResultAlias = InjectedScriptHost.savedResultAlias;
+
+    let defineGetter = (key, value) => {
+        if (typeof value !== "function") {
+            let originalValue = value;
+            value = function() { return originalValue; };
+        }
+
+        this.__defineGetter__("$" + key, value);
+        if (savedResultAlias)
+            this.__defineGetter__(savedResultAlias + key, value);
+    };
+
+    if ("_lastResult" in injectedScript)
+        defineGetter("_", injectedScript._lastResult);
+
+    if ("_exceptionValue" in injectedScript)
+        defineGetter("exception", injectedScript._exceptionValue);
+
+    if ("_eventValue" in injectedScript)
+        defineGetter("event", injectedScript._eventValue);
 
     // $0
-    this.__defineGetter__("$0", bind(commandLineAPIImpl._inspectedObject, commandLineAPIImpl));
+    defineGetter("0", bind(commandLineAPIImpl._inspectedObject, commandLineAPIImpl));
 
     // $1-$99
     for (let i = 1; i <= injectedScript._savedResults.length; ++i)
-        this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i));
+        defineGetter(i, bind(injectedScript._savedResult, injectedScript, i));
 
     // Command Line API methods.
     for (let i = 0; i < CommandLineAPI.methods.length; ++i) {
index eed35dd..9491f55 100644 (file)
@@ -1,5 +1,65 @@
 2019-08-05  Devin Rousso  <drousso@apple.com>
 
+        Can't use $0, $1 etc when inspecting Google Docs pages because the content uses these for function names
+        https://bugs.webkit.org/show_bug.cgi?id=195834
+
+        Reviewed by Joseph Pecoraro.
+
+        Allow the user to alias saved results by providing a different prefix (e.g. "$") from within
+        Web Inspector. When changing the alias, all existing saved results will update to be
+        reference-able from the new alias.
+
+        * UserInterface/Controllers/RuntimeManager.js:
+        (WI.RuntimeManager):
+        (WI.RuntimeManager.preferredSavedResultPrefix): Added.
+        (WI.RuntimeManager.prototype.initializeTarget):
+        * UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:
+        (WI.JavaScriptRuntimeCompletionProvider.completionControllerCompletionsNeeded.receivedPropertyNames):
+
+        * UserInterface/Base/Setting.js:
+        * UserInterface/Base/Main.js:
+        (WI.contentLoaded):
+        (WI.contentLoaded.updateConsoleSavedResultPrefixCSSVariable): Added.
+        * UserInterface/Views/ConsoleMessageView.js:
+        (WI.ConsoleMessageView.prototype.toClipboardString):
+        (WI.ConsoleMessageView.prototype.removeEventListeners): Added.
+        (WI.ConsoleMessageView.prototype._appendSavedResultIndex):
+        (WI.ConsoleMessageView.prototype._appendSavedResultIndex.updateSavedVariableText): Added.
+        (WI.ConsoleMessageView.prototype._rootPropertyPathForObject):
+        (WI.ConsoleMessageView.prototype._rootPropertyPathForObject.prefixSavedResultIndex): Added.
+        * UserInterface/Views/LogContentView.js:
+        (WI.LogContentView.prototype._sessionStarted):
+        (WI.LogContentView.prototype._logCleared):
+        * UserInterface/Views/DOMTreeOutline.css:
+        (.tree-outline.dom.show-last-selected li.last-selected > span::after):
+        * UserInterface/Views/QuickConsole.js:
+        (WI.QuickConsole):
+        (WI.QuickConsole.prototype.closed):
+        (WI.QuickConsole.prototype._updateAutomaticExecutionContextPathComponentTooltip): Added.
+        Listen for changes to the setting that holds the current saved result alias and update any
+        related UI accordingly.
+
+        * UserInterface/Views/SettingsTabContentView.js:
+        (WI.SettingsTabContentView.prototype._createConsoleSettingsView):
+        * UserInterface/Views/SettingsTabContentView.css:
+        (.content-view.settings > .settings-view > .container > .editor-group > .editor input[type="text"]): Added.
+        * UserInterface/Views/SettingsGroup.js:
+        (WI.SettingsGroup.prototype.addCustomEditor): Added.
+        Add an input to the Settings tab that controls the saved result prefix alias. Only allow
+        [a-zA-Z0-9_$] as values (but [0-9] cannot be used as the start).
+
+        * UserInterface/Models/PropertyPath.js:
+        (WI.PropertyPath.prototype.set pathComponent): Added.
+        Miscellaneous getters/setters.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+        * UserInterface/Test/TestHarness.js:
+        (TestHarness.prototype.newline): Added.
+        Convenience function for adding newlines to test results.
+
+2019-08-05  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Timelines: disable related agents when the tab is closed
         https://bugs.webkit.org/show_bug.cgi?id=200118
 
index c2f9ba3..94163e3 100644 (file)
@@ -467,7 +467,7 @@ localizedStrings["Events"] = "Events";
 localizedStrings["Events:"] = "Events:";
 localizedStrings["Example: \u201C%s\u201D"] = "Example: \u201C%s\u201D";
 localizedStrings["Exception with thrown value: %s"] = "Exception with thrown value: %s";
-localizedStrings["Execution context for $0"] = "Execution context for $0";
+localizedStrings["Execution context for %s"] = "Execution context for %s";
 localizedStrings["Exited Full-Screen Mode"] = "Exited Full-Screen Mode";
 localizedStrings["Expand All"] = "Expand All";
 localizedStrings["Expand columns"] = "Expand columns";
@@ -924,6 +924,7 @@ localizedStrings["Save Image"] = "Save Image";
 localizedStrings["Save Selected"] = "Save Selected";
 localizedStrings["Save configuration"] = "Save configuration";
 localizedStrings["Saved Recordings"] = "Saved Recordings";
+localizedStrings["Saved Result Alias:"] = "Saved Result Alias:";
 localizedStrings["Saved States"] = "Saved States";
 localizedStrings["Scheduling:"] = "Scheduling:";
 localizedStrings["Scheme"] = "Scheme";
index 265404b..418a6ba 100644 (file)
@@ -573,6 +573,12 @@ WI.contentLoaded = function()
     WI.tabBar.addEventListener(WI.TabBar.Event.TabBarItemRemoved, WI._rememberOpenTabs);
     WI.tabBar.addEventListener(WI.TabBar.Event.TabBarItemsReordered, WI._rememberOpenTabs);
 
+    function updateConsoleSavedResultPrefixCSSVariable() {
+        document.body.style.setProperty("--console-saved-result-prefix", "\"" + WI.RuntimeManager.preferredSavedResultPrefix() + "\"");
+    }
+    WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, updateConsoleSavedResultPrefixCSSVariable);
+    updateConsoleSavedResultPrefixCSSVariable();
+
     // Signal that the frontend is now ready to receive messages.
     WI.whenTargetsAvailable().then(() => {
         InspectorFrontendAPI.loadCompleted();
index bab9ad4..6c5ad58 100644 (file)
@@ -143,6 +143,7 @@ WI.settings = {
     canvasRecordingAutoCaptureEnabled: new WI.Setting("canvas-recording-auto-capture-enabled", false),
     canvasRecordingAutoCaptureFrameCount: new WI.Setting("canvas-recording-auto-capture-frame-count", 1),
     consoleAutoExpandTrace: new WI.Setting("console-auto-expand-trace", true),
+    consoleSavedResultAlias: new WI.Setting("console-saved-result-alias", ""),
     cssChangesPerNode: new WI.Setting("css-changes-per-node", false),
     clearLogOnNavigate: new WI.Setting("clear-log-on-navigate", true),
     clearNetworkOnNavigate: new WI.Setting("clear-network-on-navigate", true),
index 235a0db..165958d 100644 (file)
@@ -229,9 +229,19 @@ WI.JavaScriptRuntimeCompletionProvider = class JavaScriptRuntimeCompletionProvid
                 for (let name of commandLineAPI)
                     propertyNames[name] = true;
 
+                let savedResultAlias = WI.settings.consoleSavedResultAlias.value;
+                if (savedResultAlias) {
+                    propertyNames[savedResultAlias + "0"] = true;
+                    propertyNames[savedResultAlias + "_"] = true;
+                }
+
                 // FIXME: Due to caching, sometimes old $n values show up as completion results even though they are not available. We should clear that proactively.
-                for (var i = 1; i <= WI.ConsoleCommandResultMessage.maximumSavedResultIndex; ++i)
+                for (var i = 1; i <= WI.ConsoleCommandResultMessage.maximumSavedResultIndex; ++i) {
                     propertyNames["$" + i] = true;
+
+                    if (savedResultAlias)
+                        propertyNames[savedResultAlias + i] = true;
+                }
             }
 
             propertyNames = Object.keys(propertyNames);
index 1c51d77..6b6ca66 100644 (file)
@@ -31,6 +31,14 @@ WI.RuntimeManager = class RuntimeManager extends WI.Object
 
         this._activeExecutionContext = null;
 
+        WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, (event) => {
+            for (let target of WI.targets) {
+                // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
+                if (target.RuntimeAgent.setSavedResultAlias)
+                    target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value);
+            }
+        });
+
         WI.Frame.addEventListener(WI.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this);
     }
 
@@ -42,6 +50,14 @@ WI.RuntimeManager = class RuntimeManager extends WI.Object
         return !!InspectorBackend.domains.Runtime.awaitPromise;
     }
 
+    static preferredSavedResultPrefix()
+    {
+        // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
+        if (!InspectorBackend.domains.Runtime.setSavedResultAlias)
+            return "$";
+        return WI.settings.consoleSavedResultAlias.value || "$";
+    }
+
     // Target
 
     initializeTarget(target)
@@ -52,9 +68,13 @@ WI.RuntimeManager = class RuntimeManager extends WI.Object
         if (target.RuntimeAgent.enableTypeProfiler && WI.settings.showJavaScriptTypeInformation.value)
             target.RuntimeAgent.enableTypeProfiler();
 
-        // COMPATIBILITY (iOS 10): Runtime.enableControlFlowProfiler did not exist
+        // COMPATIBILITY (iOS 10): Runtime.enableControlFlowProfiler did not exist.
         if (target.RuntimeAgent.enableControlFlowProfiler && WI.settings.enableControlFlowProfiler.value)
             target.RuntimeAgent.enableControlFlowProfiler();
+
+        // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
+        if (target.RuntimeAgent.setSavedResultAlias && WI.settings.consoleSavedResultAlias.value)
+            target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value);
     }
 
     // Public
index 05bb034..ab43f53 100644 (file)
@@ -55,7 +55,9 @@ WI.PropertyPath = class PropertyPath
     get object() { return this._object; }
     get parent() { return this._parent; }
     get isPrototype() { return this._isPrototype; }
+
     get pathComponent() { return this._pathComponent; }
+    set pathComponent(pathComponent) { this._pathComponent = pathComponent; }
 
     get rootObject()
     {
index 64534d6..0a7b7a9 100644 (file)
@@ -97,6 +97,11 @@ TestHarness = class TestHarness extends WI.Object
             this.addResult(message);
     }
 
+    newline()
+    {
+        this.log("");
+    }
+
     json(object, filter)
     {
         this.log(JSON.stringify(object, filter || null, 2));
index 06c5d1a..c569c93 100644 (file)
@@ -203,7 +203,7 @@ WI.ConsoleMessageView = class ConsoleMessageView extends WI.Object
     {
         let clipboardString = this._messageBodyElement.innerText.removeWordBreakCharacters();
         if (this._message.savedResultIndex)
-            clipboardString = clipboardString.replace(/\s*=\s*(\$\d+)$/, "");
+            clipboardString = clipboardString.replace(new RegExp(`\\s*=\\s*(${WI.RuntimeManager.preferredSavedResultPrefix()}\\d+)$`), "");
 
         let hasStackTrace = this._shouldShowStackTrace();
         if (!hasStackTrace) {
@@ -240,6 +240,12 @@ WI.ConsoleMessageView = class ConsoleMessageView extends WI.Object
         return clipboardString;
     }
 
+    removeEventListeners()
+    {
+        // FIXME: <https://webkit.org/b/196956> Web Inspector: use weak collections for holding event listeners
+        WI.settings.consoleSavedResultAlias.removeEventListener(null, null, this);
+    }
+
     // Private
 
     _appendMessageTextAndArguments(element)
@@ -346,7 +352,8 @@ WI.ConsoleMessageView = class ConsoleMessageView extends WI.Object
 
     _appendSavedResultIndex(element)
     {
-        if (!this._message.savedResultIndex)
+        let savedResultIndex = this._message.savedResultIndex;
+        if (!savedResultIndex)
             return;
 
         console.assert(this._message instanceof WI.ConsoleCommandResultMessage);
@@ -354,7 +361,13 @@ WI.ConsoleMessageView = class ConsoleMessageView extends WI.Object
 
         var savedVariableElement = document.createElement("span");
         savedVariableElement.classList.add("console-saved-variable");
-        savedVariableElement.textContent = " = $" + this._message.savedResultIndex;
+
+        // FIXME: <https://webkit.org/b/196956> Web Inspector: use weak collections for holding event listeners
+        function updateSavedVariableText() {
+            savedVariableElement.textContent = " = " + WI.RuntimeManager.preferredSavedResultPrefix() + savedResultIndex;
+        }
+        WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, updateSavedVariableText, this);
+        updateSavedVariableText();
 
         if (this._objectTree)
             this._objectTree.appendTitleSuffix(savedVariableElement);
@@ -692,10 +705,22 @@ WI.ConsoleMessageView = class ConsoleMessageView extends WI.Object
 
     _rootPropertyPathForObject(object)
     {
-        if (!this._message.savedResultIndex)
+        let savedResultIndex = this._message.savedResultIndex;
+        if (!savedResultIndex)
             return null;
 
-        return new WI.PropertyPath(object, "$" + this._message.savedResultIndex);
+        function prefixSavedResultIndex() {
+            return WI.RuntimeManager.preferredSavedResultPrefix() + savedResultIndex;
+        }
+
+        let propertyPath = new WI.PropertyPath(object, prefixSavedResultIndex());
+
+        // FIXME: <https://webkit.org/b/196956> Web Inspector: use weak collections for holding event listeners
+        WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, (event) => {
+            propertyPath.pathComponent = prefixSavedResultIndex();
+        }, this);
+
+        return propertyPath;
     }
 
     _formatWithSubstitutionString(parameters, formattedResult)
index 0538683..9fc9cde 100644 (file)
@@ -71,7 +71,7 @@
 }
 
 .tree-outline.dom.show-last-selected li.last-selected > span::after {
-    content: " = $0";
+    content: " = " var(--console-saved-result-prefix) "0";
     color: var(--console-secondary-text-color);
     position: absolute;
     white-space: pre;
index 80f82d7..243e4cb 100644 (file)
@@ -394,6 +394,11 @@ WI.LogContentView = class LogContentView extends WI.ContentView
             return;
         }
 
+        for (let messageElement of this._allMessageElements()) {
+            if (messageElement.__messageView)
+                messageElement.__messageView.removeEventListeners();
+        }
+
         const isFirstSession = false;
         const newSessionReason = event.data.wasReloaded ? WI.ConsoleSession.NewSessionReason.PageReloaded : WI.ConsoleSession.NewSessionReason.PageNavigated;
         this._logViewController.startNewSession(isFirstSession, {newSessionReason, timestamp: event.data.timestamp});
@@ -825,6 +830,11 @@ WI.LogContentView = class LogContentView extends WI.ContentView
         for (let item of this._scopeBar.items)
             item.element.classList.remove("unread");
 
+        for (let messageElement of this._allMessageElements()) {
+            if (messageElement.__messageView)
+                messageElement.__messageView.removeEventListeners();
+        }
+
         this._logViewController.clear();
         this._nestingLevel = 0;
 
index 672deaa..6cf6d27 100644 (file)
@@ -33,7 +33,7 @@ WI.QuickConsole = class QuickConsole extends WI.View
         this._toggleOrFocusKeyboardShortcut.implicitlyPreventsDefault = false;
 
         this._automaticExecutionContextPathComponent = this._createExecutionContextPathComponent(null, WI.UIString("Auto"));
-        this._automaticExecutionContextPathComponent.tooltip = WI.UIString("Execution context for $0");
+        this._updateAutomaticExecutionContextPathComponentTooltip();
 
         this._mainExecutionContextPathComponent = null;
         this._otherExecutionContextPathComponents = [];
@@ -71,6 +71,8 @@ WI.QuickConsole = class QuickConsole extends WI.View
 
         this.initializeMainExecutionContextPathComponent();
 
+        WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, this._updateAutomaticExecutionContextPathComponentTooltip, this);
+
         WI.consoleDrawer.toggleButtonShortcutTooltip(this._toggleOrFocusKeyboardShortcut);
         WI.consoleDrawer.addEventListener(WI.ConsoleDrawer.Event.CollapsedStateChanged, this._updateStyles, this);
 
@@ -98,6 +100,7 @@ WI.QuickConsole = class QuickConsole extends WI.View
 
     closed()
     {
+        WI.settings.consoleSavedResultAlias.removeEventListener(null, null, this);
         WI.Frame.removeEventListener(null, null, this);
         WI.debuggerManager.removeEventListener(null, null, this);
         WI.runtimeManager.removeEventListener(null, null, this);
@@ -167,6 +170,11 @@ WI.QuickConsole = class QuickConsole extends WI.View
         return changed;
     }
 
+    _updateAutomaticExecutionContextPathComponentTooltip()
+    {
+        this._automaticExecutionContextPathComponent.tooltip = WI.UIString("Execution context for %s").format(WI.RuntimeManager.preferredSavedResultPrefix() + "0");
+    }
+
     _handleMouseDown(event)
     {
         if (event.target !== this.element)
index e851dbb..43f00f8 100644 (file)
@@ -64,4 +64,11 @@ WI.SettingsGroup = class SettingsGroup extends WI.Object
         this._editorGroupElement.append(editor.element);
         return editor;
     }
+
+    addCustomEditor()
+    {
+        let element = this._editorGroupElement.appendChild(document.createElement("div"));
+        element.classList.add("editor");
+        return element;
+    }
 };
index ebfab86..345d7b9 100644 (file)
     --settings-editor-child-margin-top: -2px;
 }
 
+.content-view.settings > .settings-view > .container > .editor-group > .editor input[type="text"] {
+    padding-top: 0;
+    padding-bottom: 0;
+    vertical-align: 1px;
+
+    /* Vertically align <input> with the group title text. */
+    --settings-editor-child-margin-top: -2px;
+}
+
 @media (prefers-color-scheme: dark) {
     .content-view.settings .navigation-bar {
         background-color: var(--background-color-content);
index 08c14cd..6bf69ab 100644 (file)
@@ -260,6 +260,30 @@ WI.SettingsTabContentView = class SettingsTabContentView extends WI.TabContentVi
     {
         let consoleSettingsView = new WI.SettingsView("console", WI.UIString("Console"));
 
+        // COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
+        if (InspectorBackend.domains.Runtime.setSavedResultAlias) {
+            let consoleSavedResultAliasEditor = consoleSettingsView.addGroupWithCustomEditor(WI.UIString("Saved Result Alias:"));
+
+            let consoleSavedResultAliasInput = consoleSavedResultAliasEditor.appendChild(document.createElement("input"));
+            consoleSavedResultAliasInput.type = "text";
+            consoleSavedResultAliasInput.value = WI.settings.consoleSavedResultAlias.value;
+            consoleSavedResultAliasInput.placeholder = WI.unlocalizedString("$");
+            consoleSavedResultAliasInput.addEventListener("keydown", (event) => {
+                if (!/[a-zA-Z0-9_$]/.test(event.key) || (consoleSavedResultAliasInput.selectionStart === 0 && /[0-9]/.test(event.key))) {
+                    event.preventDefault();
+                    InspectorFrontendHost.beep();
+                }
+            });
+            consoleSavedResultAliasInput.addEventListener("input", (event) => {
+                let savedResultAlias = consoleSavedResultAliasInput.value;
+                if (savedResultAlias === "$")
+                    savedResultAlias = "";
+                WI.settings.consoleSavedResultAlias.value = savedResultAlias;
+            });
+
+            consoleSettingsView.addSeparator();
+        }
+
         consoleSettingsView.addSetting(WI.UIString("Traces:"), WI.settings.consoleAutoExpandTrace, WI.UIString("Auto-expand"));
 
         if (WI.ConsoleManager.supportsLogChannels()) {
index 02e8859..d953ee3 100644 (file)
@@ -52,6 +52,12 @@ WI.SettingsView = class SettingsView extends WI.View
         return settingsGroup.addCustomSetting(editorType, options);
     }
 
+    addGroupWithCustomEditor(title, element)
+    {
+        let settingsGroup = this.addGroup(title);
+        return settingsGroup.addCustomEditor();
+    }
+
     addGroup(title)
     {
         let settingsGroup = new WI.SettingsGroup(title);