Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add webUtils module with getPathForFile method #38776

Merged
merged 22 commits into from Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
813b7f8
feat: add blinkUtils module with getPathForFile method
MarshallOfSound Jun 13, 2023
335c8f3
add error
MarshallOfSound Jun 13, 2023
64c9dcb
refactor: update per PR feedback
MarshallOfSound Jun 23, 2023
8e7455b
chore: update patches
MarshallOfSound Jun 23, 2023
9e1e6cb
oops
MarshallOfSound Jun 23, 2023
e063b12
chore: update patches
MarshallOfSound Aug 28, 2023
c52aca0
Merge remote-tracking branch 'origin/main' into replace-file-path
MarshallOfSound Sep 1, 2023
4e87064
chore: update patches
patchup[bot] Sep 1, 2023
d11f4ad
feat: add blinkUtils module with getPathForFile method
MarshallOfSound Jun 13, 2023
2032bf2
add error
MarshallOfSound Jun 13, 2023
8100988
refactor: update per PR feedback
MarshallOfSound Jun 23, 2023
30b7294
chore: update patches
MarshallOfSound Jun 23, 2023
75b2d89
oops
MarshallOfSound Jun 23, 2023
26790cf
chore: update patches
MarshallOfSound Aug 28, 2023
6ff3dde
chore: update patches
MarshallOfSound Sep 6, 2023
7d5cac1
Merge branch 'replace-file-path' of github.com:electron/electron into…
MarshallOfSound Sep 6, 2023
0d85e7c
Merge remote-tracking branch 'origin/main' into replace-file-path
MarshallOfSound Nov 9, 2023
cfd9ed5
chore: update patches
MarshallOfSound Nov 9, 2023
55ccd5f
fix: provide isolate to WebBlob::FromV8Value
MarshallOfSound Nov 14, 2023
64c51ba
Merge remote-tracking branch 'origin/main' into replace-file-path
MarshallOfSound Nov 20, 2023
0049ff8
chore: add tests
MarshallOfSound Nov 20, 2023
7185791
build: fix depshash mismatch on arm64 macOS
MarshallOfSound Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config/base.yml
Expand Up @@ -2165,7 +2165,7 @@ jobs:
<<: *env-ninja-status
<<: *env-macos-build
<<: *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:
- electron-build:
persist: true
Expand Down
5 changes: 5 additions & 0 deletions docs/api/file-object.md
Expand Up @@ -2,6 +2,11 @@

> 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
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
Expand Down
26 changes: 26 additions & 0 deletions docs/api/web-utils.md
@@ -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])
```
2 changes: 1 addition & 1 deletion docs/tutorial/sandbox.md
Expand Up @@ -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,
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)
* [`timers`](https://nodejs.org/api/timers.html)
* [`url`](https://nodejs.org/api/url.html)
Expand Down
4 changes: 4 additions & 0 deletions filenames.auto.gni
Expand Up @@ -67,6 +67,7 @@ auto_filenames = {
"docs/api/web-frame-main.md",
"docs/api/web-frame.md",
"docs/api/web-request.md",
"docs/api/web-utils.md",
"docs/api/webview-tag.md",
"docs/api/window-open.md",
"docs/api/structures/bluetooth-device.md",
Expand Down Expand Up @@ -151,6 +152,7 @@ auto_filenames = {
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/api/web-utils.ts",
"lib/renderer/common-init.ts",
"lib/renderer/inspector.ts",
"lib/renderer/ipc-renderer-internal-utils.ts",
Expand Down Expand Up @@ -281,6 +283,7 @@ auto_filenames = {
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/api/web-utils.ts",
"lib/renderer/common-init.ts",
"lib/renderer/init.ts",
"lib/renderer/inspector.ts",
Expand Down Expand Up @@ -318,6 +321,7 @@ auto_filenames = {
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.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.ts",
"lib/worker/init.ts",
Expand Down
2 changes: 2 additions & 0 deletions filenames.gni
Expand Up @@ -682,6 +682,8 @@ filenames = {
"shell/renderer/api/electron_api_spell_check_client.cc",
"shell/renderer/api/electron_api_spell_check_client.h",
"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.h",
"shell/renderer/content_settings_observer.cc",
Expand Down
3 changes: 2 additions & 1 deletion lib/renderer/api/module-list.ts
Expand Up @@ -4,5 +4,6 @@ export const rendererModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'contextBridge', loader: () => require('./context-bridge') },
{ name: 'crashReporter', loader: () => require('./crash-reporter') },
{ name: 'ipcRenderer', loader: () => require('./ipc-renderer') },
{ name: 'webFrame', loader: () => require('./web-frame') }
{ name: 'webFrame', loader: () => require('./web-frame') },
{ name: 'webUtils', loader: () => require('./web-utils') }
];
3 changes: 3 additions & 0 deletions lib/renderer/api/web-utils.ts
@@ -0,0 +1,3 @@
const binding = process._linkedBinding('electron_renderer_web_utils');

export const getPathForFile = binding.getPathForFile;
4 changes: 4 additions & 0 deletions lib/sandboxed_renderer/api/module-list.ts
Expand Up @@ -18,5 +18,9 @@ export const moduleList: ElectronInternal.ModuleEntry[] = [
{
name: 'webFrame',
loader: () => require('@electron/internal/renderer/api/web-frame')
},
{
name: 'webUtils',
loader: () => require('@electron/internal/renderer/api/web-utils')
}
];
3 changes: 2 additions & 1 deletion patches/chromium/.patches
Expand Up @@ -130,8 +130,9 @@ fix_harden_blink_scriptstate_maybefrom.patch
chore_add_buildflag_guard_around_new_include.patch
fix_use_delegated_generic_capturer_when_available.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
revert_remove_the_allowaggressivethrottlingwithwebsocket_feature.patch
fix_activate_background_material_on_windows.patch
feat_allow_passing_of_objecttemplate_to_objecttemplatebuilder.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>();
1 change: 1 addition & 0 deletions shell/common/node_bindings.cc
Expand Up @@ -90,6 +90,7 @@
V(electron_common_v8_util)

#define ELECTRON_RENDERER_BINDINGS(V) \
V(electron_renderer_web_utils) \
MarshallOfSound marked this conversation as resolved.
Show resolved Hide resolved
V(electron_renderer_context_bridge) \
V(electron_renderer_crash_reporter) \
V(electron_renderer_ipc) \
Expand Down
40 changes: 40 additions & 0 deletions shell/renderer/api/electron_api_web_utils.cc
@@ -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)
16 changes: 16 additions & 0 deletions shell/renderer/api/electron_api_web_utils.h
@@ -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_
53 changes: 53 additions & 0 deletions spec/api-web-utils-spec.ts
@@ -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 changes: 5 additions & 0 deletions spec/fixtures/pages/file-input.html
@@ -0,0 +1,5 @@
<html>
<body>
<input type="file" id="file" />
</body>
</html>