feat: add webUtils module with getPathForFile method (#38776)

* feat: add blinkUtils module with getPathForFile method

This is designed to replace the File.path augmentation
we currently have in place to allow apps to get the filesystem
path for a file that blink has a representation of.

File.path is non-standard and messes with certain websites, using
a method like this is effectively 0-cost and removes one of the final
deviations we have with web standards.

* add error

* refactor: update per PR feedback

* chore: update patches

* oops

* chore: update patches

* chore: update patches

* feat: add blinkUtils module with getPathForFile method

This is designed to replace the File.path augmentation
we currently have in place to allow apps to get the filesystem
path for a file that blink has a representation of.

File.path is non-standard and messes with certain websites, using
a method like this is effectively 0-cost and removes one of the final
deviations we have with web standards.

* add error

* refactor: update per PR feedback

* chore: update patches

* oops

* chore: update patches

* chore: update patches

* chore: update patches

* fix: provide isolate to WebBlob::FromV8Value

* chore: add tests

* build: fix depshash mismatch on arm64 macOS

---------

Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
This commit is contained in:
Samuel Attard 2023-11-20 15:59:36 -08:00 коммит произвёл GitHub
Родитель 2c03b8fd6b
Коммит d6bb9b40b0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 211 добавлений и 4 удалений

Просмотреть файл

@ -2165,7 +2165,7 @@ jobs:
<<: *env-ninja-status <<: *env-ninja-status
<<: *env-macos-build <<: *env-macos-build
<<: *env-apple-silicon <<: *env-apple-silicon
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac' GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac --custom-var=host_cpu=arm64'
steps: steps:
- electron-build: - electron-build:
persist: true persist: true

Просмотреть файл

@ -2,6 +2,11 @@
> Use the HTML5 `File` API to work natively with files on the filesystem. > Use the HTML5 `File` API to work natively with files on the filesystem.
> **Warning**
> The `path` property that Electron adds to the `File` interface is deprecated
> and **will** be removed in a future Electron release. We recommend you
> use `webUtils.getPathForFile` instead.
The DOM's File interface provides abstraction around native files in order to The DOM's File interface provides abstraction around native files in order to
let users work on native files directly with the HTML5 file API. Electron has let users work on native files directly with the HTML5 file API. Electron has
added a `path` attribute to the `File` interface which exposes the file's real added a `path` attribute to the `File` interface which exposes the file's real

26
docs/api/web-utils.md Normal file
Просмотреть файл

@ -0,0 +1,26 @@
# webUtils
> A utility layer to interact with Web API objects (Files, Blobs, etc.)
Process: [Renderer](../glossary.md#renderer-process)
## Methods
The `webUtils` module has the following methods:
### `webUtils.getPathForFile(file)`
* `file` File - A web [File](https://developer.mozilla.org/en-US/docs/Web/API/File) object.
Returns `string` - The file system path that this `File` object points to. In the case where the object passed in is not a `File` object an exception is thrown. In the case where the File object passed in was constructed in JS and is not backed by a file on disk an empty string is returned.
This method superceded the previous augmentation to the `File` object with the `path` property. An example is included below.
```js
// Before
const oldPath = document.querySelector('input').files[0].path
// After
const { webUtils } = require('electron')
const newPath = webUtils.getPathForFile(document.querySelector('input').files[0])
```

Просмотреть файл

@ -46,7 +46,7 @@ scripts attached to sandboxed renderers will still have a polyfilled subset of N
APIs available. A `require` function similar to Node's `require` module is exposed, APIs available. A `require` function similar to Node's `require` module is exposed,
but can only import a subset of Electron and Node's built-in modules: but can only import a subset of Electron and Node's built-in modules:
* `electron` (following renderer process modules: `contextBridge`, `crashReporter`, `ipcRenderer`, `nativeImage`, `webFrame`) * `electron` (following renderer process modules: `contextBridge`, `crashReporter`, `ipcRenderer`, `nativeImage`, `webFrame`, `webUtils`)
* [`events`](https://nodejs.org/api/events.html) * [`events`](https://nodejs.org/api/events.html)
* [`timers`](https://nodejs.org/api/timers.html) * [`timers`](https://nodejs.org/api/timers.html)
* [`url`](https://nodejs.org/api/url.html) * [`url`](https://nodejs.org/api/url.html)

Просмотреть файл

@ -67,6 +67,7 @@ auto_filenames = {
"docs/api/web-frame-main.md", "docs/api/web-frame-main.md",
"docs/api/web-frame.md", "docs/api/web-frame.md",
"docs/api/web-request.md", "docs/api/web-request.md",
"docs/api/web-utils.md",
"docs/api/webview-tag.md", "docs/api/webview-tag.md",
"docs/api/window-open.md", "docs/api/window-open.md",
"docs/api/structures/bluetooth-device.md", "docs/api/structures/bluetooth-device.md",
@ -151,6 +152,7 @@ auto_filenames = {
"lib/renderer/api/crash-reporter.ts", "lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/web-frame.ts", "lib/renderer/api/web-frame.ts",
"lib/renderer/api/web-utils.ts",
"lib/renderer/common-init.ts", "lib/renderer/common-init.ts",
"lib/renderer/inspector.ts", "lib/renderer/inspector.ts",
"lib/renderer/ipc-renderer-internal-utils.ts", "lib/renderer/ipc-renderer-internal-utils.ts",
@ -281,6 +283,7 @@ auto_filenames = {
"lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts", "lib/renderer/api/module-list.ts",
"lib/renderer/api/web-frame.ts", "lib/renderer/api/web-frame.ts",
"lib/renderer/api/web-utils.ts",
"lib/renderer/common-init.ts", "lib/renderer/common-init.ts",
"lib/renderer/init.ts", "lib/renderer/init.ts",
"lib/renderer/inspector.ts", "lib/renderer/inspector.ts",
@ -318,6 +321,7 @@ auto_filenames = {
"lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts", "lib/renderer/api/module-list.ts",
"lib/renderer/api/web-frame.ts", "lib/renderer/api/web-frame.ts",
"lib/renderer/api/web-utils.ts",
"lib/renderer/ipc-renderer-internal-utils.ts", "lib/renderer/ipc-renderer-internal-utils.ts",
"lib/renderer/ipc-renderer-internal.ts", "lib/renderer/ipc-renderer-internal.ts",
"lib/worker/init.ts", "lib/worker/init.ts",

Просмотреть файл

@ -682,6 +682,8 @@ filenames = {
"shell/renderer/api/electron_api_spell_check_client.cc", "shell/renderer/api/electron_api_spell_check_client.cc",
"shell/renderer/api/electron_api_spell_check_client.h", "shell/renderer/api/electron_api_spell_check_client.h",
"shell/renderer/api/electron_api_web_frame.cc", "shell/renderer/api/electron_api_web_frame.cc",
"shell/renderer/api/electron_api_web_utils.cc",
"shell/renderer/api/electron_api_web_utils.h",
"shell/renderer/browser_exposed_renderer_interfaces.cc", "shell/renderer/browser_exposed_renderer_interfaces.cc",
"shell/renderer/browser_exposed_renderer_interfaces.h", "shell/renderer/browser_exposed_renderer_interfaces.h",
"shell/renderer/content_settings_observer.cc", "shell/renderer/content_settings_observer.cc",

Просмотреть файл

@ -4,5 +4,6 @@ export const rendererModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'contextBridge', loader: () => require('./context-bridge') }, { name: 'contextBridge', loader: () => require('./context-bridge') },
{ name: 'crashReporter', loader: () => require('./crash-reporter') }, { name: 'crashReporter', loader: () => require('./crash-reporter') },
{ name: 'ipcRenderer', loader: () => require('./ipc-renderer') }, { name: 'ipcRenderer', loader: () => require('./ipc-renderer') },
{ name: 'webFrame', loader: () => require('./web-frame') } { name: 'webFrame', loader: () => require('./web-frame') },
{ name: 'webUtils', loader: () => require('./web-utils') }
]; ];

Просмотреть файл

@ -0,0 +1,3 @@
const binding = process._linkedBinding('electron_renderer_web_utils');
export const getPathForFile = binding.getPathForFile;

Просмотреть файл

@ -18,5 +18,9 @@ export const moduleList: ElectronInternal.ModuleEntry[] = [
{ {
name: 'webFrame', name: 'webFrame',
loader: () => require('@electron/internal/renderer/api/web-frame') loader: () => require('@electron/internal/renderer/api/web-frame')
},
{
name: 'webUtils',
loader: () => require('@electron/internal/renderer/api/web-utils')
} }
]; ];

Просмотреть файл

@ -130,8 +130,9 @@ fix_harden_blink_scriptstate_maybefrom.patch
chore_add_buildflag_guard_around_new_include.patch chore_add_buildflag_guard_around_new_include.patch
fix_use_delegated_generic_capturer_when_available.patch fix_use_delegated_generic_capturer_when_available.patch
build_remove_ent_content_analysis_assert.patch build_remove_ent_content_analysis_assert.patch
fix_activate_background_material_on_windows.patch expose_webblob_path_to_allow_embedders_to_get_file_paths.patch
fix_move_autopipsettingshelper_behind_branding_buildflag.patch fix_move_autopipsettingshelper_behind_branding_buildflag.patch
revert_remove_the_allowaggressivethrottlingwithwebsocket_feature.patch revert_remove_the_allowaggressivethrottlingwithwebsocket_feature.patch
fix_activate_background_material_on_windows.patch
feat_allow_passing_of_objecttemplate_to_objecttemplatebuilder.patch feat_allow_passing_of_objecttemplate_to_objecttemplatebuilder.patch
chore_remove_check_is_test_on_script_injection_tracker.patch chore_remove_check_is_test_on_script_injection_tracker.patch

Просмотреть файл

@ -0,0 +1,46 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samuel Attard <marshallofsound@electronjs.org>
Date: Tue, 13 Jun 2023 15:36:04 -0700
Subject: expose WebBlob::Path to allow embedders to get file paths
Used to replace the File.path augmentation Electron currently implements. This is safer / more web-standard technique.
diff --git a/third_party/blink/public/web/web_blob.h b/third_party/blink/public/web/web_blob.h
index 384a59138db11ea38028f844dd67e328ebffbe7b..f153997c2afccef1fa1b64ee5f162c28a2d07e5d 100644
--- a/third_party/blink/public/web/web_blob.h
+++ b/third_party/blink/public/web/web_blob.h
@@ -67,6 +67,7 @@ class BLINK_EXPORT WebBlob {
void Reset();
void Assign(const WebBlob&);
WebString Uuid();
+ std::string Path();
bool IsNull() const { return private_.IsNull(); }
diff --git a/third_party/blink/renderer/core/exported/web_blob.cc b/third_party/blink/renderer/core/exported/web_blob.cc
index ce7b5e229789d606df5e74461f09e2e1db59fc95..b1bf2affa5b7f10d9b45d062a2ce0479f5a3b80a 100644
--- a/third_party/blink/renderer/core/exported/web_blob.cc
+++ b/third_party/blink/renderer/core/exported/web_blob.cc
@@ -40,6 +40,7 @@
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/core/fileapi/file_backed_blob_factory_dispatcher.h"
+#include "third_party/blink/renderer/core/fileapi/file.h"
#include "third_party/blink/renderer/platform/blob/blob_data.h"
#include "third_party/blink/renderer/platform/file_metadata.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -83,6 +84,14 @@ WebString WebBlob::Uuid() {
return private_->Uuid();
}
+std::string WebBlob::Path() {
+ if (!private_.Get())
+ return "";
+ if (private_->IsFile() && private_->HasBackingFile())
+ return To<File>(private_.Get())->GetPath().Utf8();
+ return "";
+}
+
v8::Local<v8::Value> WebBlob::ToV8Value(v8::Isolate* isolate) {
if (!private_.Get())
return v8::Local<v8::Value>();

Просмотреть файл

@ -90,6 +90,7 @@
V(electron_common_v8_util) V(electron_common_v8_util)
#define ELECTRON_RENDERER_BINDINGS(V) \ #define ELECTRON_RENDERER_BINDINGS(V) \
V(electron_renderer_web_utils) \
V(electron_renderer_context_bridge) \ V(electron_renderer_context_bridge) \
V(electron_renderer_crash_reporter) \ V(electron_renderer_crash_reporter) \
V(electron_renderer_ipc) \ V(electron_renderer_ipc) \

Просмотреть файл

@ -0,0 +1,40 @@
// Copyright (c) 2023 Salesforce, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/renderer/api/electron_api_web_utils.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/error_thrower.h"
#include "shell/common/node_includes.h"
#include "third_party/blink/public/web/web_blob.h"
namespace electron::api::web_utils {
std::string GetPathForFile(v8::Isolate* isolate, v8::Local<v8::Value> file) {
blink::WebBlob blob = blink::WebBlob::FromV8Value(isolate, file);
if (blob.IsNull()) {
gin_helper::ErrorThrower(isolate).ThrowTypeError(
"getPathForFile expected to receive a File object but one was not "
"provided");
return "";
}
return blob.Path();
}
} // namespace electron::api::web_utils
namespace {
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod("getPathForFile", &electron::api::web_utils::GetPathForFile);
}
} // namespace
NODE_LINKED_BINDING_CONTEXT_AWARE(electron_renderer_web_utils, Initialize)

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) 2023 Salesforce, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_RENDERER_API_ELECTRON_API_WEB_UTILS_H_
#define ELECTRON_SHELL_RENDERER_API_ELECTRON_API_WEB_UTILS_H_
#include "v8/include/v8.h"
namespace electron::api::web_utils {
std::string GetPathForFile(v8::Isolate* isolate, v8::Local<v8::Value> file);
} // namespace electron::api::web_utils
#endif // ELECTRON_SHELL_RENDERER_API_ELECTRON_API_WEB_UTILS_H_

Просмотреть файл

@ -0,0 +1,53 @@
import { expect } from 'chai';
import * as path from 'node:path';
import { BrowserWindow } from 'electron/main';
import { defer } from './lib/spec-helpers';
// import { once } from 'node:events';
describe('webUtils module', () => {
const fixtures = path.resolve(__dirname, 'fixtures');
describe('getPathForFile', () => {
it('returns nothing for a Blob', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
sandbox: false
}
});
defer(() => w.close());
await w.loadFile(path.resolve(fixtures, 'pages', 'file-input.html'));
const pathFromWebUtils = await w.webContents.executeJavaScript('require("electron").webUtils.getPathForFile(new Blob([1, 2, 3]))');
expect(pathFromWebUtils).to.equal('');
});
it('reports the correct path for a File object', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
sandbox: false
}
});
defer(() => w.close());
await w.loadFile(path.resolve(fixtures, 'pages', 'file-input.html'));
const { debugger: debug } = w.webContents;
debug.attach();
try {
const { root: { nodeId } } = await debug.sendCommand('DOM.getDocument');
const { nodeId: inputNodeId } = await debug.sendCommand('DOM.querySelector', { nodeId, selector: 'input' });
await debug.sendCommand('DOM.setFileInputFiles', {
files: [__filename],
nodeId: inputNodeId
});
const pathFromWebUtils = await w.webContents.executeJavaScript('require("electron").webUtils.getPathForFile(document.querySelector("input").files[0])');
expect(pathFromWebUtils).to.equal(__filename);
} finally {
debug.detach();
}
});
});
});

5
spec/fixtures/pages/file-input.html поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
<html>
<body>
<input type="file" id="file" />
</body>
</html>