Skip to content
Permalink
Browse files
Web Inspector: replace the two "Export Audit" and "Export Result" but…
…tons with a single button that has a picker in the `NSSavePanel`

https://bugs.webkit.org/show_bug.cgi?id=241214
<rdar://problem/76775840>

Reviewed by Patrick Angle.

It's odd to have only one "Import" button but two "Export *" buttons in the Audits Tab, Graphics Tab,
etc. Ideally, there'd be just one "Export" button that lets the developer choose what kind of data
to save (i.e. all formats are provided up front, and the developer picks what's actually used).

* Source/WebInspectorUI/UserInterface/Base/FileUtilities.js:
(WI.FileUtilities.canSave): Added.
(WI.FileUtilities.async save): Added.
(WI.FileUtilities.save): Deleted.
* Source/WebCore/inspector/InspectorFrontendHost.idl:
* Source/WebCore/inspector/InspectorFrontendHost.h:
* Source/WebCore/inspector/InspectorFrontendHost.cpp:
(WebCore::InspectorFrontendHost::canSave):
(WebCore::InspectorFrontendHost::save):
Introduce a new `SaveMode` that's used to both check for multi-format saving support and indicate
that a particular save operation has multiple formats. Always require an array of `SaveData` to be
provided when saving, but only expect a single item when the save operation is not multi-format.

* Source/WebInspectorUI/UserInterface/Base/Main.js:
(WI._contextMenuRequested):
(WI._updateDownloadTabBarButton):
(WI._save):
(WI._saveAs):
(WI.archiveMainFrame):
(WI.canArchiveMainFrame):
* Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js:
(WI.AuditManager.prototype.export):
(WI.AuditManager.prototype.export.dataForObject): Added.
* Source/WebInspectorUI/UserInterface/Debug/ProtocolTrace.js:
(WI.ProtocolTrace.prototype.get saveMode): Added.
* Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js:
(WI.TimelineRecording.prototype.get exportMode): Added.
(WI.TimelineRecording.prototype.canExport):
* Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js:
(WI.AuditTestContentView):
(WI.AuditTestContentView.prototype.get navigationItems):
(WI.AuditTestContentView.prototype.get supportsSave):
(WI.AuditTestContentView.prototype.get saveMode): Added.
(WI.AuditTestContentView.prototype.get saveData):
(WI.AuditTestContentView.prototype._export): Added.
(WI.AuditTestContentView.prototype._updateExportNavigationItems):
(WI.AuditTestContentView.prototype._handleExportTestButtonNavigationItemClicked):
(WI.AuditTestContentView.prototype._handleExportResultButtonNavigationItemClicked):
(WI.AuditTestContentView.prototype._handleExportButtonNavigationItemClicked): Added.
(WI.AuditTestContentView.prototype._exportResult): Deleted.
* Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js:
(WI.AuditTreeElement.prototype.populateContextMenu):
* Source/WebInspectorUI/UserInterface/Views/ClusterContentView.js:
(WI.ClusterContentView.prototype.get supportsSave):
(WI.ClusterContentView.prototype.get saveMode): Added.
(WI.ClusterContentView.prototype.get saveData):
* Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.js:
(WI.ConsoleMessageView.prototype._handleContextMenu):
* Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js:
(WI.appendContextMenuItemsForSourceCode):
* Source/WebInspectorUI/UserInterface/Views/DOMTreeContentView.js:
(WI.DOMTreeContentView.prototype.get saveMode): Added.
* Source/WebInspectorUI/UserInterface/Views/HeapSnapshotContentView.js:
(WI.HeapSnapshotContentView):
(WI.HeapSnapshotContentView.prototype._exportSnapshot):
* Source/WebInspectorUI/UserInterface/Views/LogContentView.js:
(WI.LogContentView.prototype.get saveMode): Added.
(WI.LogContentView.prototype._handleContextMenuEvent):
* Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js:
(WI.NetworkTableContentView):
(WI.NetworkTableContentView.prototype.get saveMode): Added.
(WI.NetworkTableContentView.prototype._canExportHAR):
(WI.NetworkTableContentView.prototype._exportHAR):
* Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js:
(WI.RecordingContentView):
(WI.RecordingContentView.prototype.get supportsSave):
(WI.RecordingContentView.prototype.get saveMode): Added.
(WI.RecordingContentView.prototype.get saveData):
(WI.RecordingContentView.prototype._export): Added.
(WI.RecordingContentView.prototype._exportRecording):
(WI.RecordingContentView.prototype._exportReduction):
(WI.RecordingContentView.prototype._updateExportButton):
(WI.RecordingContentView.prototype._handleExportNavigationItemClicked):
* Source/WebInspectorUI/UserInterface/Views/ResourceContentView.js:
(WI.ResourceContentView.prototype.get saveMode): Added.
* Source/WebInspectorUI/UserInterface/Views/ScriptContentView.js:
(WI.ScriptContentView.prototype.get saveMode): Added.
* Source/WebInspectorUI/UserInterface/Views/ShaderProgramContentView.js:
(WI.ShaderProgramContentView):
(WI.ShaderProgramContentView.prototype.get supportsSave):
(WI.ShaderProgramContentView.prototype.get saveMode): Added.
(WI.ShaderProgramContentView.prototype.get saveData):
* Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationSection.js:
(WI.SpreadsheetCSSStyleDeclarationSection.prototype.initialLayout):
(WI.SpreadsheetCSSStyleDeclarationSection.prototype._save):
* Source/WebInspectorUI/UserInterface/Views/TextContentView.js:
(WI.TextContentView.prototype.get saveMode):
* Source/WebInspectorUI/UserInterface/Views/TextResourceContentView.js:
(WI.TextResourceContentView.prototype.get saveData):
* Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js:
(WI.TimelineRecordingContentView.prototype.get saveMode):
(WI.TimelineRecordingContentView.prototype._exportTimelineRecording):
Indicate the kind of `SaveMode` that's used. When multi-format is supported, use it to unify the
export buttons (if applicable). Also make sure to only enable the export button(s) if the desired
`SaveMode` is supported.

* Source/WebKit/WebProcess/Inspector/WebInspectorUI.h:
* Source/WebKit/WebProcess/Inspector/WebInspectorUI.cpp:
(WebKit::WebInspectorUI::save):
(WebKit::WebInspectorUI::canSave):
* Source/WebKit/WebProcess/Inspector/gtk/WebInspectorUIGtk.cpp:
(WebKit::WebInspectorUI::canSave):
(WebKit::RemoteWebInspectorUI::localizedStringsURL const): Deleted.
* Source/WebKit/WebProcess/Inspector/mac/WebInspectorUIMac.mm:
(WebKit::WebInspectorUI::canSave):
(WebKit::WebInspectorUI::localizedStringsURL const):
(WebKit::webInspectorUILocalizedStringsURL): Deleted.
(WebKit::RemoteWebInspectorUI::localizedStringsURL const): Deleted.
* Source/WebKit/WebProcess/Inspector/win/WebInspectorUIWin.cpp:
(WebKit::WebInspectorUI::canSave):
(WebKit::RemoteWebInspectorUI::localizedStringsURL const): Deleted.
* Source/WebKit/WebProcess/Inspector/RemoteWebInspectorUI.h:
* Source/WebKit/WebProcess/Inspector/RemoteWebInspectorUI.cpp:
(WebKit::RemoteWebInspectorUI::save):
(WebKit::RemoteWebInspectorUI::canSave): Added.
(WebKit::RemoteWebInspectorUI::canLoad): Added.
* Source/WebKit/WebProcess/Inspector/gtk/RemoteWebInspectorUIGtk.cpp: Added.
(WebKit::RemoteWebInspectorUI::canSave):
(WebKit::RemoteWebInspectorUI::canLoad):
(WebKit::RemoteWebInspectorUI::localizedStringsURL const):
* Source/WebKit/WebProcess/Inspector/mac/RemoteWebInspectorUIMac.mm: Added.
(WebKit::RemoteWebInspectorUI::canSave):
(WebKit::RemoteWebInspectorUI::canLoad):
(WebKit::RemoteWebInspectorUI::localizedStringsURL const):
* Source/WebKit/WebProcess/Inspector/win/RemoteWebInspectorUIWin.cpp: Added.
(WebKit::RemoteWebInspectorUI::canSave):
(WebKit::RemoteWebInspectorUI::canLoad):
(WebKit::RemoteWebInspectorUI::localizedStringsURL const):
Currently, macOS supports both single and multi-format saving, GTK supports single format saving,
and windows supports neither.
Drive-by: Add `RemoteWebInspectorProxy*` files for each platform instead of shoving the code into
          the related `WebInspectorProxy*` (of the same platform).

* Source/WebKit/UIProcess/Inspector/WebInspectorUIProxy.messages.in:
* Source/WebKit/UIProcess/Inspector/WebInspectorUIProxy.h:
* Source/WebKit/UIProcess/Inspector/WebInspectorUIProxy.cpp:
(WebKit::WebInspectorUIProxy::save):
(WebKit::WebInspectorUIProxy::platformSave):
* Source/WebKit/UIProcess/Inspector/gtk/WebInspectorUIProxyGtk.cpp:
(WebKit::WebInspectorUIProxy::platformSave):
* Source/WebKit/UIProcess/Inspector/mac/WebInspectorUIProxyMac.mm:
(-[WKWebInspectorUISaveController initWithSaveDatas:savePanel:]): Added.
(-[WKWebInspectorUISaveController content]): Added.
(-[WKWebInspectorUISaveController base64Encoded]): Added.
(-[WKWebInspectorUISaveController _updateSavePanel]): Added.
(-[WKWebInspectorUISaveController _popUpButtonAction:]): Added.
(WebKit::WebInspectorUIProxy::showSavePanel):
(WebKit::WebInspectorUIProxy::platformSave): Added.
* Source/WebKit/UIProcess/Inspector/win/WebInspectorUIProxyWin.cpp:
(WebKit::WebInspectorUIProxy::platformSave):
* Source/WebKit/UIProcess/Inspector/RemoteWebInspectorUIProxy.messages.in:
* Source/WebKit/UIProcess/Inspector/RemoteWebInspectorUIProxy.h:
* Source/WebKit/UIProcess/Inspector/RemoteWebInspectorUIProxy.cpp:
(WebKit::RemoteWebInspectorUIProxy::save):
(WebKit::RemoteWebInspectorUIProxy::platformSave):
* Source/WebKit/UIProcess/Inspector/gtk/RemoteWebInspectorUIProxyGtk.cpp:
(WebKit::RemoteWebInspectorUIProxy::platformSave):
* Source/WebKit/UIProcess/Inspector/mac/RemoteWebInspectorUIProxyMac.mm:
(WebKit::RemoteWebInspectorUIProxy::platformSave):
* Source/WebKit/UIProcess/Inspector/win/RemoteWebInspectorUIProxyWin.cpp:
(WebKit::RemoteWebInspectorUIProxy::platformSave):
On macOS, create a shared function to show a `NSSavePanel` with an `accessoryView` that lets the
developer choose which format to save with. On GTK, there should only ever be a single `SaveData`
provided (since GTK only supports single format saving), so use that.

* Source/WebKitLegacy/mac/WebCoreSupport/WebInspectorClient.h:
* Source/WebKitLegacy/mac/WebCoreSupport/WebInspectorClient.mm:
(WebInspectorFrontendClient::canSave):
(WebInspectorFrontendClient::save):
* Source/WebKitLegacy/ios/WebCoreSupport/WebInspectorClientIOS.mm:
(WebInspectorFrontendClient::save):
WK1 only supports single format saving, meaning that there should only ever be a single `SaveData`
provided, so use that.

* Source/WebCore/inspector/InspectorFrontendClient.h:
(WebCore::InspectorFrontendClient::SaveData::encode const):
(WebCore::InspectorFrontendClient::SaveData::decode):
IPC stuff.

* Source/WebCore/inspector/InspectorFrontendClientLocal.h:
(WebCore::InspectorFrontendClientLocal::canSave):
(WebCore::InspectorFrontendClientLocal::save):
Testing code doesn't (need to) know how to save things.

* Source/WebInspectorUI/UserInterface/Base/URLUtilities.js:
(WI.urlWithoutExtension): Added.
* LayoutTests/inspector/unit-tests/url-utilities.html:
* LayoutTests/inspector/unit-tests/url-utilities-expected.txt:
Drive-by: Added a utility method to help in a `console.assert`.

* Source/WebCore/en.lproj/Localizable.strings:
* Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js:
* Source/WebKit/PlatformWin.cmake:
* Source/WebKit/SourcesCocoa.txt:
* Source/WebKit/SourcesGTK.txt:
* Source/WebKit/WebKit.xcodeproj/project.pbxproj:

Canonical link: https://commits.webkit.org/251295@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@295240 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
dcrousso committed Jun 3, 2022
1 parent 5c455ef commit 742899262f4017f19c042e12ad6f60910c746862
Show file tree
Hide file tree
Showing 61 changed files with 1,175 additions and 382 deletions.
@@ -468,6 +468,86 @@ PASS: Display name of 'http://' should be 'http://'.
PASS: Display name of 'http://example.com:65537' should be 'http://example.com:65537'.
PASS: Display name of 'http://user@pass:example.com/' should be 'http://user@pass:example.com/'.

-- Running test case: WI.urlWithoutExtension
PASS: Removing extension of 'http://example.com' should be 'http://example.com/'.
PASS: Removing extension of 'http://example.com?query' should be 'http://example.com/'.
PASS: Removing extension of 'http://example.com?query=value' should be 'http://example.com/'.
PASS: Removing extension of 'http://example.com#fragment' should be 'http://example.com/'.
PASS: Removing extension of 'http://example.com?query#fragment' should be 'http://example.com/'.
PASS: Removing extension of 'http://example.com?query=value#fragment' should be 'http://example.com/'.
PASS: Removing extension of 'http://example.com/a' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a?query' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a?query=value' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a#fragment' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a?query#fragment' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a?query=value#fragment' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a/' should be 'http://example.com/a/'.
PASS: Removing extension of 'http://example.com/a/?query' should be 'http://example.com/a/'.
PASS: Removing extension of 'http://example.com/a/?query=value' should be 'http://example.com/a/'.
PASS: Removing extension of 'http://example.com/a/#fragment' should be 'http://example.com/a/'.
PASS: Removing extension of 'http://example.com/a/?query#fragment' should be 'http://example.com/a/'.
PASS: Removing extension of 'http://example.com/a/?query=value#fragment' should be 'http://example.com/a/'.
PASS: Removing extension of 'http://example.com/a.b' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b?query' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b?query=value' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b#fragment' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b?query#fragment' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b?query=value#fragment' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b/' should be 'http://example.com/a.b/'.
PASS: Removing extension of 'http://example.com/a.b/?query' should be 'http://example.com/a.b/'.
PASS: Removing extension of 'http://example.com/a.b/?query=value' should be 'http://example.com/a.b/'.
PASS: Removing extension of 'http://example.com/a.b/#fragment' should be 'http://example.com/a.b/'.
PASS: Removing extension of 'http://example.com/a.b/?query#fragment' should be 'http://example.com/a.b/'.
PASS: Removing extension of 'http://example.com/a.b/?query=value#fragment' should be 'http://example.com/a.b/'.
PASS: Removing extension of 'http://example.com/a.b.c' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b.c?query' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b.c?query=value' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b.c#fragment' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b.c?query#fragment' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b.c?query=value#fragment' should be 'http://example.com/a'.
PASS: Removing extension of 'http://example.com/a.b.c/' should be 'http://example.com/a.b.c/'.
PASS: Removing extension of 'http://example.com/a.b.c/?query' should be 'http://example.com/a.b.c/'.
PASS: Removing extension of 'http://example.com/a.b.c/?query=value' should be 'http://example.com/a.b.c/'.
PASS: Removing extension of 'http://example.com/a.b.c/#fragment' should be 'http://example.com/a.b.c/'.
PASS: Removing extension of 'http://example.com/a.b.c/?query#fragment' should be 'http://example.com/a.b.c/'.
PASS: Removing extension of 'http://example.com/a.b.c/?query=value#fragment' should be 'http://example.com/a.b.c/'.
PASS: Removing extension of 'http://example.com/a/b' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b?query' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b?query=value' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b#fragment' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b?query#fragment' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b?query=value#fragment' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b/' should be 'http://example.com/a/b/'.
PASS: Removing extension of 'http://example.com/a/b/?query' should be 'http://example.com/a/b/'.
PASS: Removing extension of 'http://example.com/a/b/?query=value' should be 'http://example.com/a/b/'.
PASS: Removing extension of 'http://example.com/a/b/#fragment' should be 'http://example.com/a/b/'.
PASS: Removing extension of 'http://example.com/a/b/?query#fragment' should be 'http://example.com/a/b/'.
PASS: Removing extension of 'http://example.com/a/b/?query=value#fragment' should be 'http://example.com/a/b/'.
PASS: Removing extension of 'http://example.com/a/b.c' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c?query' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c?query=value' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c#fragment' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c?query#fragment' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c?query=value#fragment' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c/' should be 'http://example.com/a/b.c/'.
PASS: Removing extension of 'http://example.com/a/b.c/?query' should be 'http://example.com/a/b.c/'.
PASS: Removing extension of 'http://example.com/a/b.c/?query=value' should be 'http://example.com/a/b.c/'.
PASS: Removing extension of 'http://example.com/a/b.c/#fragment' should be 'http://example.com/a/b.c/'.
PASS: Removing extension of 'http://example.com/a/b.c/?query#fragment' should be 'http://example.com/a/b.c/'.
PASS: Removing extension of 'http://example.com/a/b.c/?query=value#fragment' should be 'http://example.com/a/b.c/'.
PASS: Removing extension of 'http://example.com/a/b.c.d' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c.d?query' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c.d?query=value' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c.d#fragment' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c.d?query#fragment' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c.d?query=value#fragment' should be 'http://example.com/a/b'.
PASS: Removing extension of 'http://example.com/a/b.c.d/' should be 'http://example.com/a/b.c.d/'.
PASS: Removing extension of 'http://example.com/a/b.c.d/?query' should be 'http://example.com/a/b.c.d/'.
PASS: Removing extension of 'http://example.com/a/b.c.d/?query=value' should be 'http://example.com/a/b.c.d/'.
PASS: Removing extension of 'http://example.com/a/b.c.d/#fragment' should be 'http://example.com/a/b.c.d/'.
PASS: Removing extension of 'http://example.com/a/b.c.d/?query#fragment' should be 'http://example.com/a/b.c.d/'.
PASS: Removing extension of 'http://example.com/a/b.c.d/?query=value#fragment' should be 'http://example.com/a/b.c.d/'.

-- Running test case: WI.urlWithoutFragment
PASS: Removing fragment of 'http://example.com' should be 'http://example.com/'.
PASS: Removing fragment of 'http://example.com#frag' should be 'http://example.com/'.
@@ -539,6 +539,40 @@
},
});

suite.addTestCase({
name: "WI.urlWithoutExtension",
test() {
function test(url, expected) {
InspectorTest.expectEqual(WI.urlWithoutExtension(url), expected, `Removing extension of '${url}' should be '${expected}'.`);
}

function testVariations(url, expected) {
test(url, expected);
test(url + "?query", expected);
test(url + "?query=value", expected);
test(url + "#fragment", expected);
test(url + "?query#fragment", expected);
test(url + "?query=value#fragment", expected);
}

testVariations("http://example.com", "http://example.com/");
testVariations("http://example.com/a", "http://example.com/a");
testVariations("http://example.com/a/", "http://example.com/a/");
testVariations("http://example.com/a.b", "http://example.com/a");
testVariations("http://example.com/a.b/", "http://example.com/a.b/");
testVariations("http://example.com/a.b.c", "http://example.com/a");
testVariations("http://example.com/a.b.c/", "http://example.com/a.b.c/");
testVariations("http://example.com/a/b", "http://example.com/a/b");
testVariations("http://example.com/a/b/", "http://example.com/a/b/");
testVariations("http://example.com/a/b.c", "http://example.com/a/b");
testVariations("http://example.com/a/b.c/", "http://example.com/a/b.c/");
testVariations("http://example.com/a/b.c.d", "http://example.com/a/b");
testVariations("http://example.com/a/b.c.d/", "http://example.com/a/b.c.d/");

return true;
}
});

suite.addTestCase({
name: "WI.urlWithoutFragment",
test() {
@@ -493,6 +493,9 @@
/* Display name for text track kind 'forced'. */
"Forced (text track)" = "Forced";

/* Label for the save data format selector when saving data in Web Inspector */
"Format:" = "Format:";

/* Undo action name */
"Format Block (Undo action name)" = "Formatting";

@@ -108,9 +108,26 @@ class InspectorFrontendClient : public CanMakeWeakPtr<InspectorFrontendClient> {

WEBCORE_EXPORT virtual void openURLExternally(const String& url) = 0;
WEBCORE_EXPORT virtual void revealFileExternally(const String& path) = 0;
virtual bool canSave() = 0;
virtual void save(const String& url, const String& content, bool base64Encoded, bool forceSaveAs) = 0;

// Keep in sync with `WI.FileUtilities.SaveMode` and `InspectorFrontendHost::SaveMode`.
enum class SaveMode : uint8_t {
SingleFile,
FileVariants,
};
struct SaveData {
String displayType;
String url;
String content;
bool base64Encoded;

template<class Encoder> void encode(Encoder&) const;
template<class Decoder> static std::optional<SaveData> decode(Decoder&);
};
virtual bool canSave(SaveMode) = 0;
virtual void save(Vector<SaveData>&&, bool forceSaveAs) = 0;

virtual void append(const String& url, const String& content) = 0;

virtual bool canLoad() = 0;
virtual void load(const String& path, CompletionHandler<void(const String&)>&&) = 0;

@@ -141,6 +158,39 @@ class InspectorFrontendClient : public CanMakeWeakPtr<InspectorFrontendClient> {
WEBCORE_EXPORT virtual bool isUnderTest() = 0;
};

template<class Encoder>
void InspectorFrontendClient::SaveData::encode(Encoder& encoder) const
{
encoder << displayType;
encoder << url;
encoder << content;
encoder << base64Encoded;
}

template<class Decoder>
std::optional<InspectorFrontendClient::SaveData> InspectorFrontendClient::SaveData::decode(Decoder& decoder)
{
#define DECODE(name, type) \
std::optional<type> name; \
decoder >> name; \
if (!name) \
return std::nullopt; \

DECODE(displayType, String)
DECODE(url, String)
DECODE(content, String)
DECODE(base64Encoded, bool)

#undef DECODE

return { {
WTFMove(*displayType),
WTFMove(*url),
WTFMove(*content),
WTFMove(*base64Encoded),
} };
}

} // namespace WebCore

namespace WTF {
@@ -154,4 +204,12 @@ template<> struct EnumTraits<WebCore::InspectorFrontendClient::Appearance> {
>;
};

template<> struct EnumTraits<WebCore::InspectorFrontendClient::SaveMode> {
using values = EnumValues<
WebCore::InspectorFrontendClient::SaveMode,
WebCore::InspectorFrontendClient::SaveMode::SingleFile,
WebCore::InspectorFrontendClient::SaveMode::FileVariants
>;
};

} // namespace WTF
@@ -82,8 +82,8 @@ class InspectorFrontendClientLocal : public InspectorFrontendClient {
WEBCORE_EXPORT void changeSheetRect(const FloatRect&) final;
WEBCORE_EXPORT void openURLExternally(const String& url) final;
void revealFileExternally(const String&) override { }
bool canSave() override { return false; }
void save(const String&, const String&, bool, bool) override { }
bool canSave(InspectorFrontendClient::SaveMode) override { return false; }
void save(Vector<InspectorFrontendClient::SaveData>&&, bool /* forceSaveAs */) override { }
void append(const String&, const String&) override { }
bool canLoad() override { return false; }
void load(const String&, CompletionHandler<void(const String&)>&& completionHandler) override { completionHandler(nullString()); }
@@ -51,7 +51,6 @@
#include "HitTestResult.h"
#include "InspectorController.h"
#include "InspectorDebuggableType.h"
#include "InspectorFrontendClient.h"
#include "JSDOMConvertInterface.h"
#include "JSDOMExceptionHandling.h"
#include "JSDOMPromiseDeferred.h"
@@ -458,17 +457,17 @@ void InspectorFrontendHost::revealFileExternally(const String& path)
m_client->revealFileExternally(path);
}

bool InspectorFrontendHost::canSave()
bool InspectorFrontendHost::canSave(SaveMode saveMode)
{
if (m_client)
return m_client->canSave();
return m_client->canSave(saveMode);
return false;
}

void InspectorFrontendHost::save(const String& url, const String& content, bool base64Encoded, bool forceSaveAs)
void InspectorFrontendHost::save(Vector<SaveData>&& saveDatas, bool forceSaveAs)
{
if (m_client)
m_client->save(url, content, base64Encoded, forceSaveAs);
m_client->save(WTFMove(saveDatas), forceSaveAs);
}

void InspectorFrontendHost::append(const String& url, const String& content)
@@ -31,6 +31,7 @@
#include "ContextMenu.h"
#include "ContextMenuProvider.h"
#include "ExceptionOr.h"
#include "InspectorFrontendClient.h"
#include <wtf/RefCounted.h>
#include <wtf/Vector.h>
#include <wtf/text/WTFString.h>
@@ -45,7 +46,6 @@ class Event;
class File;
class FrontendMenuProvider;
class HTMLIFrameElement;
class InspectorFrontendClient;
class Page;
class Path2D;

@@ -110,13 +110,20 @@ class InspectorFrontendHost : public RefCounted<InspectorFrontendHost> {

void copyText(const String& text);
void killText(const String& text, bool shouldPrependToKillRing, bool shouldStartNewSequence);

void openURLExternally(const String& url);
void revealFileExternally(const String& path);
bool canSave();
void save(const String& url, const String& content, bool base64Encoded, bool forceSaveAs);

using SaveMode = InspectorFrontendClient::SaveMode;
using SaveData = InspectorFrontendClient::SaveData;
bool canSave(SaveMode);
void save(Vector<SaveData>&&, bool forceSaveAs);

void append(const String& url, const String& content);

bool canLoad();
void load(const String& path, Ref<DeferredPromise>&&);

void close(const String& url);

bool canPickColorFromScreen();
@@ -70,13 +70,18 @@

undefined copyText(DOMString text);
undefined killText(DOMString text, boolean shouldPrependToKillRing, boolean shouldStartNewSequence);

undefined openURLExternally(DOMString url);
undefined revealFileExternally(DOMString path);
boolean canSave();
undefined save(DOMString url, DOMString content, boolean base64Encoded, boolean forceSaveAs);

boolean canSave(SaveMode saveMode);
undefined save(sequence<SaveData> saveDatas, boolean forceSaveAs);

undefined append(DOMString url, DOMString content);

boolean canLoad();
[NewObject] Promise<DOMString> load(DOMString path);

undefined close(DOMString url);

boolean canPickColorFromScreen();
@@ -118,6 +123,19 @@
undefined setPath(CanvasRenderingContext2D context, Path2D path);
};

// Keep in sync with `WI.FileUtilities.SaveMode` and `InspectorFrontendClient::SaveMode`.
enum SaveMode {
"single-file",
"file-variants"
};

dictionary SaveData {
DOMString displayType;
DOMString url;
DOMString content;
boolean base64Encoded;
};

dictionary ContextMenuItem {
DOMString type;
DOMString label;
@@ -205,6 +205,7 @@ localizedStrings["Attribute"] = "Attribute";
/* A submenu item of 'Break On' that breaks (pauses) before DOM attribute is modified */
localizedStrings["Attribute Modified @ DOM Breakpoint"] = "Attribute Modified";
localizedStrings["Attributes"] = "Attributes";
localizedStrings["Audit"] = "Audit";
localizedStrings["Audit Error: %s"] = "Audit Error: %s";
/* Name of Audit Tab */
localizedStrings["Audit Tab Name"] = "Audit";
@@ -1205,6 +1206,7 @@ localizedStrings["Recordings"] = "Recordings";
localizedStrings["Redirect"] = "Redirect";
localizedStrings["Redirect Response"] = "Redirect Response";
localizedStrings["Redirects"] = "Redirects";
localizedStrings["Reduction"] = "Reduction";
localizedStrings["Reference Issue"] = "Reference Issue";
localizedStrings["Reflection"] = "Reflection";
localizedStrings["Refresh"] = "Refresh";
@@ -1267,6 +1269,7 @@ localizedStrings["Response Override @ Local Override Network Stage"] = "Response
localizedStrings["Response:"] = "Response:";
localizedStrings["Restart (%s)"] = "Restart (%s)";
localizedStrings["Restart animation"] = "Restart animation";
localizedStrings["Result"] = "Result";
localizedStrings["Result Data"] = "Result Data";
localizedStrings["Result Levels"] = "Result Levels";
localizedStrings["Results"] = "Results";

0 comments on commit 7428992

Please sign in to comment.