feat: add fileSystem to ses.setPermissionCheckHandler (#48327)
		
	feat: add fileSystem to ses.setPermissionCheckHandler Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
		
					parent
					
						
							
								99feff3965
							
						
					
				
			
			
				commit
				
					
						cf9d0448be
					
				
			
		
					 7 changed files with 325 additions and 22 deletions
				
			
		| 
						 | 
				
			
			@ -939,14 +939,18 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
 | 
			
		|||
    * `top-level-storage-access` -  Allow top-level sites to request third-party cookie access on behalf of embedded content originating from another site in the same related website set using the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API).
 | 
			
		||||
    * `usb` - Expose non-standard Universal Serial Bus (USB) compatible devices services to the web with the [WebUSB API](https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API).
 | 
			
		||||
    * `deprecated-sync-clipboard-read` _Deprecated_ - Request access to run `document.execCommand("paste")`
 | 
			
		||||
    * `fileSystem` - Access to read, write, and file management capabilities using the [File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API).
 | 
			
		||||
  * `requestingOrigin` string - The origin URL of the permission check
 | 
			
		||||
  * `details` Object - Some properties are only available on certain permission types.
 | 
			
		||||
    * `embeddingOrigin` string (optional) - The origin of the frame embedding the frame that made the permission check.  Only set for cross-origin sub frames making permission checks.
 | 
			
		||||
    * `securityOrigin` string (optional) - The security origin of the `media` check.
 | 
			
		||||
    * `mediaType` string (optional) - The type of media access being requested, can be `video`,
 | 
			
		||||
      `audio` or `unknown`
 | 
			
		||||
      `audio` or `unknown`.
 | 
			
		||||
    * `requestingUrl` string (optional) - The last URL the requesting frame loaded.  This is not provided for cross-origin sub frames making permission checks.
 | 
			
		||||
    * `isMainFrame` boolean - Whether the frame making the request is the main frame
 | 
			
		||||
    * `isMainFrame` boolean - Whether the frame making the request is the main frame.
 | 
			
		||||
    * `filePath` string (optional) - The path of a `fileSystem` request.
 | 
			
		||||
    * `isDirectory` boolean (optional) - Whether a `fileSystem` request is a directory.
 | 
			
		||||
    * `fileAccessType` string (optional) - The access type of a `fileSystem` request. Can be `writable` or `readable`.
 | 
			
		||||
 | 
			
		||||
Sets the handler which can be used to respond to permission checks for the `session`.
 | 
			
		||||
Returning `true` will allow the permission and `false` will reject it.  Please note that
 | 
			
		||||
| 
						 | 
				
			
			@ -968,6 +972,9 @@ session.fromPartition('some-partition').setPermissionCheckHandler((webContents,
 | 
			
		|||
})
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
> [!NOTE]
 | 
			
		||||
> `isMainFrame` will always be `false` for a `fileSystem` request as a result of Chromium limitations.
 | 
			
		||||
 | 
			
		||||
#### `ses.setDisplayMediaRequestHandler(handler[, opts])`
 | 
			
		||||
 | 
			
		||||
* `handler` Function | null
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -142,6 +142,14 @@ void ElectronPermissionManager::SetBluetoothPairingHandler(
 | 
			
		|||
  bluetooth_pairing_handler_ = handler;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ElectronPermissionManager::HasPermissionRequestHandler() const {
 | 
			
		||||
  return !request_handler_.is_null();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ElectronPermissionManager::HasPermissionCheckHandler() const {
 | 
			
		||||
  return !check_handler_.is_null();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ElectronPermissionManager::RequestPermissionWithDetails(
 | 
			
		||||
    blink::mojom::PermissionDescriptorPtr permission,
 | 
			
		||||
    content::RenderFrameHost* render_frame_host,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,6 +82,9 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
 | 
			
		|||
  void SetProtectedUSBHandler(const ProtectedUSBHandler& handler);
 | 
			
		||||
  void SetBluetoothPairingHandler(const BluetoothPairingHandler& handler);
 | 
			
		||||
 | 
			
		||||
  bool HasPermissionRequestHandler() const;
 | 
			
		||||
  bool HasPermissionCheckHandler() const;
 | 
			
		||||
 | 
			
		||||
  void CheckBluetoothDevicePair(gin_helper::Dictionary details,
 | 
			
		||||
                                PairCallback pair_callback) const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -257,6 +257,28 @@ class FileSystemAccessPermissionContext::PermissionGrantImpl
 | 
			
		|||
  // FileSystemAccessPermissionGrant:
 | 
			
		||||
  PermissionStatus GetStatus() override {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
    auto* permission_manager =
 | 
			
		||||
        static_cast<electron::ElectronPermissionManager*>(
 | 
			
		||||
            context_->browser_context()->GetPermissionControllerDelegate());
 | 
			
		||||
    if (permission_manager && permission_manager->HasPermissionCheckHandler()) {
 | 
			
		||||
      base::Value::Dict details;
 | 
			
		||||
      details.Set("filePath", base::FilePathToValue(path_info_.path));
 | 
			
		||||
      details.Set("isDirectory", handle_type_ == HandleType::kDirectory);
 | 
			
		||||
      details.Set("fileAccessType",
 | 
			
		||||
                  type_ == GrantType::kWrite ? "writable" : "readable");
 | 
			
		||||
 | 
			
		||||
      bool granted = permission_manager->CheckPermissionWithDetails(
 | 
			
		||||
          blink::PermissionType::FILE_SYSTEM, nullptr, origin_.GetURL(),
 | 
			
		||||
          std::move(details));
 | 
			
		||||
      return granted ? PermissionStatus::GRANTED : PermissionStatus::DENIED;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return status_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  PermissionStatus GetActivePermissionStatus() {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
    return status_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -279,8 +301,8 @@ class FileSystemAccessPermissionContext::PermissionGrantImpl
 | 
			
		|||
    // Check if a permission request has already been processed previously. This
 | 
			
		||||
    // check is done first because we don't want to reset the status of a
 | 
			
		||||
    // permission if it has already been granted.
 | 
			
		||||
    if (GetStatus() != PermissionStatus::ASK || !context_) {
 | 
			
		||||
      if (GetStatus() == PermissionStatus::GRANTED) {
 | 
			
		||||
    if (GetActivePermissionStatus() != PermissionStatus::ASK || !context_) {
 | 
			
		||||
      if (GetActivePermissionStatus() == PermissionStatus::GRANTED) {
 | 
			
		||||
        SetStatus(PermissionStatus::GRANTED);
 | 
			
		||||
      }
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kRequestAborted);
 | 
			
		||||
| 
						 | 
				
			
			@ -294,7 +316,7 @@ class FileSystemAccessPermissionContext::PermissionGrantImpl
 | 
			
		|||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Don't request permission  for an inactive RenderFrameHost as the
 | 
			
		||||
    // Don't request permission for an inactive RenderFrameHost as the
 | 
			
		||||
    // page might not distinguish properly between user denying the permission
 | 
			
		||||
    // and automatic rejection.
 | 
			
		||||
    if (rfh->IsInactiveAndDisallowActivation(
 | 
			
		||||
| 
						 | 
				
			
			@ -347,7 +369,7 @@ class FileSystemAccessPermissionContext::PermissionGrantImpl
 | 
			
		|||
    permission_manager->RequestPermissionWithDetails(
 | 
			
		||||
        content::PermissionDescriptorUtil::
 | 
			
		||||
            CreatePermissionDescriptorForPermissionType(type),
 | 
			
		||||
        rfh, origin, false, std::move(details),
 | 
			
		||||
        rfh, origin, rfh->HasTransientUserActivation(), std::move(details),
 | 
			
		||||
        base::BindOnce(&PermissionGrantImpl::OnPermissionRequestResult, this,
 | 
			
		||||
                       std::move(callback)));
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -394,7 +416,8 @@ class FileSystemAccessPermissionContext::PermissionGrantImpl
 | 
			
		|||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DCHECK_EQ(entry_it->second->GetStatus(), PermissionStatus::GRANTED);
 | 
			
		||||
    DCHECK_EQ(entry_it->second->GetActivePermissionStatus(),
 | 
			
		||||
              PermissionStatus::GRANTED);
 | 
			
		||||
 | 
			
		||||
    auto* const grant_impl = entry_it->second;
 | 
			
		||||
    grant_impl->SetPath(new_path);
 | 
			
		||||
| 
						 | 
				
			
			@ -963,7 +986,8 @@ bool FileSystemAccessPermissionContext::OriginHasReadAccess(
 | 
			
		|||
  auto it = active_permissions_map_.find(origin);
 | 
			
		||||
  if (it != active_permissions_map_.end()) {
 | 
			
		||||
    return std::ranges::any_of(it->second.read_grants, [&](const auto& grant) {
 | 
			
		||||
      return grant.second->GetStatus() == PermissionStatus::GRANTED;
 | 
			
		||||
      return grant.second->GetActivePermissionStatus() ==
 | 
			
		||||
             PermissionStatus::GRANTED;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -977,7 +1001,8 @@ bool FileSystemAccessPermissionContext::OriginHasWriteAccess(
 | 
			
		|||
  auto it = active_permissions_map_.find(origin);
 | 
			
		||||
  if (it != active_permissions_map_.end()) {
 | 
			
		||||
    return std::ranges::any_of(it->second.write_grants, [&](const auto& grant) {
 | 
			
		||||
      return grant.second->GetStatus() == PermissionStatus::GRANTED;
 | 
			
		||||
      return grant.second->GetActivePermissionStatus() ==
 | 
			
		||||
             PermissionStatus::GRANTED;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1031,7 +1056,7 @@ bool FileSystemAccessPermissionContext::AncestorHasActivePermission(
 | 
			
		|||
       parent = parent.DirName()) {
 | 
			
		||||
    auto i = relevant_grants.find(parent);
 | 
			
		||||
    if (i != relevant_grants.end() && i->second &&
 | 
			
		||||
        i->second->GetStatus() == PermissionStatus::GRANTED) {
 | 
			
		||||
        i->second->GetActivePermissionStatus() == PermissionStatus::GRANTED) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -1054,7 +1079,7 @@ void FileSystemAccessPermissionContext::PermissionGrantDestroyed(
 | 
			
		|||
  // be granted but won't be visible in any UI because the permission context
 | 
			
		||||
  // isn't tracking them anymore.
 | 
			
		||||
  if (grant_it == grants.end()) {
 | 
			
		||||
    DCHECK_EQ(PermissionStatus::DENIED, grant->GetStatus());
 | 
			
		||||
    DCHECK_EQ(PermissionStatus::DENIED, grant->GetActivePermissionStatus());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -901,13 +901,15 @@ describe('chromium features', () => {
 | 
			
		|||
  });
 | 
			
		||||
 | 
			
		||||
  describe('File System API,', () => {
 | 
			
		||||
    afterEach(closeAllWindows);
 | 
			
		||||
    let w: BrowserWindow | null = null;
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
      session.defaultSession.setPermissionRequestHandler(null);
 | 
			
		||||
      closeAllWindows();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('allows access by default to reading an OPFS file', async () => {
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
      w = new BrowserWindow({
 | 
			
		||||
        show: false,
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -929,7 +931,7 @@ describe('chromium features', () => {
 | 
			
		|||
    });
 | 
			
		||||
 | 
			
		||||
    it('fileHandle.queryPermission by default has permission to read and write to OPFS files', async () => {
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
      w = new BrowserWindow({
 | 
			
		||||
        show: false,
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -951,7 +953,8 @@ describe('chromium features', () => {
 | 
			
		|||
    });
 | 
			
		||||
 | 
			
		||||
    it('fileHandle.requestPermission automatically grants permission to read and write to OPFS files', async () => {
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
      w = new BrowserWindow({
 | 
			
		||||
        show: false,
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          partition: 'file-system-spec',
 | 
			
		||||
| 
						 | 
				
			
			@ -971,8 +974,8 @@ describe('chromium features', () => {
 | 
			
		|||
      expect(status).to.equal('granted');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('requests permission when trying to create a writable file handle', (done) => {
 | 
			
		||||
      const writablePath = path.join(fixturesPath, 'file-system', 'test-writable.html');
 | 
			
		||||
    it('allows permission when trying to create a writable file handle', (done) => {
 | 
			
		||||
      const writablePath = path.join(fixturesPath, 'file-system', 'test-perms.html');
 | 
			
		||||
      const testFile = path.join(fixturesPath, 'file-system', 'test.txt');
 | 
			
		||||
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
| 
						 | 
				
			
			@ -1000,9 +1003,9 @@ describe('chromium features', () => {
 | 
			
		|||
 | 
			
		||||
      ipcMain.once('did-create-file-handle', async () => {
 | 
			
		||||
        const result = await w.webContents.executeJavaScript(`
 | 
			
		||||
          new Promise((resolve, reject) => {
 | 
			
		||||
          new Promise(async (resolve, reject) => {
 | 
			
		||||
            try {
 | 
			
		||||
              const writable = fileHandle.createWritable();
 | 
			
		||||
              const writable = await handle.createWritable();
 | 
			
		||||
              resolve(true);
 | 
			
		||||
            } catch {
 | 
			
		||||
              resolve(false);
 | 
			
		||||
| 
						 | 
				
			
			@ -1021,6 +1024,258 @@ describe('chromium features', () => {
 | 
			
		|||
        w.webContents.paste();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('denies permission when trying to create a writable file handle', (done) => {
 | 
			
		||||
      const writablePath = path.join(fixturesPath, 'file-system', 'test-perms.html');
 | 
			
		||||
      const testFile = path.join(fixturesPath, 'file-system', 'test.txt');
 | 
			
		||||
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          contextIsolation: false,
 | 
			
		||||
          sandbox: false
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.webContents.session.setPermissionRequestHandler((wc, permission, callback, details) => {
 | 
			
		||||
        expect(permission).to.equal('fileSystem');
 | 
			
		||||
 | 
			
		||||
        const { href } = url.pathToFileURL(writablePath);
 | 
			
		||||
        expect(details).to.deep.equal({
 | 
			
		||||
          fileAccessType: 'writable',
 | 
			
		||||
          isDirectory: false,
 | 
			
		||||
          isMainFrame: true,
 | 
			
		||||
          filePath: testFile,
 | 
			
		||||
          requestingUrl: href
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        callback(false);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      ipcMain.once('did-create-file-handle', async () => {
 | 
			
		||||
        const result = await w.webContents.executeJavaScript(`
 | 
			
		||||
          new Promise(async (resolve, reject) => {
 | 
			
		||||
            try {
 | 
			
		||||
              const writable = await handle.createWritable();
 | 
			
		||||
              resolve(true);
 | 
			
		||||
            } catch {
 | 
			
		||||
              resolve(false);
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        `, true);
 | 
			
		||||
        expect(result).to.be.false();
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.loadFile(writablePath);
 | 
			
		||||
 | 
			
		||||
      w.webContents.once('did-finish-load', () => {
 | 
			
		||||
        // @ts-expect-error Undocumented testing method.
 | 
			
		||||
        clipboard._writeFilesForTesting([testFile]);
 | 
			
		||||
        w.webContents.paste();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('calls twice when trying to query a read/write file handle permissions', (done) => {
 | 
			
		||||
      const writablePath = path.join(fixturesPath, 'file-system', 'test-perms.html');
 | 
			
		||||
      const testFile = path.join(fixturesPath, 'file-system', 'test.txt');
 | 
			
		||||
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          contextIsolation: false,
 | 
			
		||||
          sandbox: false
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      let calls = 0;
 | 
			
		||||
      w.webContents.session.setPermissionCheckHandler((wc, permission, origin, details) => {
 | 
			
		||||
        if (permission === 'fileSystem') {
 | 
			
		||||
          const { fileAccessType, isDirectory, filePath } = details;
 | 
			
		||||
          expect(['writable', 'readable']).to.contain(fileAccessType);
 | 
			
		||||
          expect(isDirectory).to.be.false();
 | 
			
		||||
          expect(filePath).to.equal(testFile);
 | 
			
		||||
          calls++;
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      ipcMain.once('did-create-file-handle', async () => {
 | 
			
		||||
        const permission = await w.webContents.executeJavaScript(`
 | 
			
		||||
          new Promise(async (resolve, reject) => {
 | 
			
		||||
            try {
 | 
			
		||||
              const permission = await handle.queryPermission({ mode: 'readwrite' });
 | 
			
		||||
              resolve(permission);
 | 
			
		||||
            } catch {
 | 
			
		||||
              resolve('denied');
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        `, true);
 | 
			
		||||
        expect(permission).to.equal('granted');
 | 
			
		||||
        expect(calls).to.equal(2);
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.loadFile(writablePath);
 | 
			
		||||
 | 
			
		||||
      w.webContents.once('did-finish-load', () => {
 | 
			
		||||
        // @ts-expect-error Undocumented testing method.
 | 
			
		||||
        clipboard._writeFilesForTesting([testFile]);
 | 
			
		||||
        w.webContents.paste();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('correctly denies permissions after creating a readable directory handle', (done) => {
 | 
			
		||||
      const permPath = path.join(fixturesPath, 'file-system', 'test-perms.html');
 | 
			
		||||
      const testDir = path.join(fixturesPath, 'file-system');
 | 
			
		||||
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          contextIsolation: false,
 | 
			
		||||
          sandbox: false
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.webContents.session.setPermissionCheckHandler((wc, permission, origin, details) => {
 | 
			
		||||
        expect(permission).to.equal('fileSystem');
 | 
			
		||||
 | 
			
		||||
        const { fileAccessType, isDirectory, filePath } = details;
 | 
			
		||||
        expect(fileAccessType).to.equal('readable');
 | 
			
		||||
        expect(isDirectory).to.be.true();
 | 
			
		||||
        expect(filePath).to.equal(testDir);
 | 
			
		||||
        return false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      ipcMain.once('did-create-directory-handle', async () => {
 | 
			
		||||
        const permission = await w.webContents.executeJavaScript(`
 | 
			
		||||
          new Promise(async (resolve, reject) => {
 | 
			
		||||
            try {
 | 
			
		||||
              const permission = await handle.queryPermission({ mode: 'read' });
 | 
			
		||||
              resolve(permission);
 | 
			
		||||
            } catch {
 | 
			
		||||
              resolve('denied');
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        `, true);
 | 
			
		||||
        expect(permission).to.equal('denied');
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.loadFile(permPath);
 | 
			
		||||
 | 
			
		||||
      w.webContents.once('did-finish-load', () => {
 | 
			
		||||
        // @ts-expect-error Undocumented testing method.
 | 
			
		||||
        clipboard._writeFilesForTesting([testDir]);
 | 
			
		||||
        w.webContents.paste();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('correctly allows permissions after creating a readable directory handle', (done) => {
 | 
			
		||||
      const permPath = path.join(fixturesPath, 'file-system', 'test-perms.html');
 | 
			
		||||
      const testDir = path.join(fixturesPath, 'file-system');
 | 
			
		||||
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          contextIsolation: false,
 | 
			
		||||
          sandbox: false
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.webContents.session.setPermissionCheckHandler((wc, permission, origin, details) => {
 | 
			
		||||
        if (permission === 'fileSystem') {
 | 
			
		||||
          const { fileAccessType, isDirectory, filePath } = details;
 | 
			
		||||
          expect(fileAccessType).to.equal('readable');
 | 
			
		||||
          expect(isDirectory).to.be.true();
 | 
			
		||||
          expect(filePath).to.equal(testDir);
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      ipcMain.once('did-create-directory-handle', async () => {
 | 
			
		||||
        const permission = await w.webContents.executeJavaScript(`
 | 
			
		||||
          new Promise(async (resolve, reject) => {
 | 
			
		||||
            try {
 | 
			
		||||
              const permission = await handle.queryPermission({ mode: 'read' });
 | 
			
		||||
              resolve(permission);
 | 
			
		||||
            } catch {
 | 
			
		||||
              resolve('denied');
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        `, true);
 | 
			
		||||
        expect(permission).to.equal('granted');
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.loadFile(permPath);
 | 
			
		||||
 | 
			
		||||
      w.webContents.once('did-finish-load', () => {
 | 
			
		||||
        // @ts-expect-error Undocumented testing method.
 | 
			
		||||
        clipboard._writeFilesForTesting([testDir]);
 | 
			
		||||
        w.webContents.paste();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('allows in-session persistence of granted file permissions', (done) => {
 | 
			
		||||
      const writablePath = path.join(fixturesPath, 'file-system', 'test-perms.html');
 | 
			
		||||
      const testFile = path.join(fixturesPath, 'file-system', 'persist.txt');
 | 
			
		||||
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          contextIsolation: false,
 | 
			
		||||
          sandbox: false
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.webContents.session.setPermissionRequestHandler((_wc, _permission, callback) => {
 | 
			
		||||
        callback(true);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.webContents.session.setPermissionCheckHandler((_wc, permission, _origin, details) => {
 | 
			
		||||
        if (permission === 'fileSystem') {
 | 
			
		||||
          const { fileAccessType, isDirectory, filePath } = details;
 | 
			
		||||
          expect(fileAccessType).to.deep.equal('readable');
 | 
			
		||||
          expect(isDirectory).to.be.false();
 | 
			
		||||
          expect(filePath).to.equal(testFile);
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      let reload = true;
 | 
			
		||||
      ipcMain.on('did-create-file-handle', async () => {
 | 
			
		||||
        if (reload) {
 | 
			
		||||
          w.webContents.reload();
 | 
			
		||||
          reload = false;
 | 
			
		||||
        } else {
 | 
			
		||||
          const permission = await w.webContents.executeJavaScript(`
 | 
			
		||||
            new Promise(async (resolve, reject) => {
 | 
			
		||||
              try {
 | 
			
		||||
                const permission = await handle.queryPermission({ mode: 'read' });
 | 
			
		||||
                resolve(permission);
 | 
			
		||||
              } catch {
 | 
			
		||||
                resolve('denied');
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
          `, true);
 | 
			
		||||
          expect(permission).to.equal('granted');
 | 
			
		||||
          done();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.loadFile(writablePath);
 | 
			
		||||
 | 
			
		||||
      w.webContents.on('did-finish-load', () => {
 | 
			
		||||
        // @ts-expect-error Undocumented testing method.
 | 
			
		||||
        clipboard._writeFilesForTesting([testFile]);
 | 
			
		||||
        w.webContents.paste();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('web workers', () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								spec/fixtures/file-system/persist.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								spec/fixtures/file-system/persist.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
hello persist
 | 
			
		||||
| 
						 | 
				
			
			@ -10,13 +10,17 @@
 | 
			
		|||
  <script>
 | 
			
		||||
    const { ipcRenderer } = require('electron')
 | 
			
		||||
 | 
			
		||||
    let fileHandle = null;
 | 
			
		||||
    let handle = null;
 | 
			
		||||
    let sent = false;
 | 
			
		||||
    window.document.onpaste = async (event) => {
 | 
			
		||||
      const fileItem = event.clipboardData.items[0];
 | 
			
		||||
      fileHandle = await fileItem.getAsFileSystemHandle();
 | 
			
		||||
      handle = await fileItem.getAsFileSystemHandle();
 | 
			
		||||
      if (!sent) {
 | 
			
		||||
        ipcRenderer.send('did-create-file-handle');
 | 
			
		||||
        if (handle.kind === 'file') {
 | 
			
		||||
          ipcRenderer.send('did-create-file-handle');
 | 
			
		||||
        } else {
 | 
			
		||||
          ipcRenderer.send('did-create-directory-handle');
 | 
			
		||||
        }
 | 
			
		||||
        sent = true;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue