Compare commits

...

175 Commits

Author SHA1 Message Date
Electron Bot
5bdb5b821b Bump v9.0.0-beta.19 2020-04-23 08:32:16 -07:00
Samuel Attard
0cce079c8f fix: do not mutate ipc instances across contexts (#23238) 2020-04-22 17:11:37 -07:00
Samuel Attard
0c67a1de8c fix: do not allow child windows to specify their own preload script (#23227) 2020-04-22 17:07:22 -07:00
trop[bot]
963ef4dbe7 fix: block custom window.open when nativeWindowOpen is true (#23222)
* fix: block custom window.open when nativeWindowOpen is true

* Update guest-window-manager.js

Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
Co-authored-by: Jeremy Apthorp <jeremya@chromium.org>
2020-04-22 17:07:10 -07:00
trop[bot]
e559af3616 fix: cherry-pick 04dab5a91b61 from chromium (#23190)
* Make HitTestResult::LocalPoint() for inline element as same as legacy layout

https://chromium-review.googlesource.com/c/chromium/src/+/2151775

* update patches

* update patches

Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: Electron Bot <anonymous@electronjs.org>
2020-04-22 13:30:49 -04:00
trop[bot]
ed2bc5a4aa fix: ensure that functions are not retained beyond their context being released (#23209)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-04-22 09:09:18 -07:00
Electron Bot
edb22be8d4 chore: bump chromium in DEPS to 83.0.4103.24 (#23218) 2020-04-22 10:37:18 -04:00
Charles Kerr
74d391baae fix: use Node's microtasks policy in node_main.cc (#23154)
Fixes #21515.
2020-04-21 13:42:59 -07:00
Electron Bot
b35a98ce2a chore: bump chromium in DEPS to 83.0.4103.21 (#23198) 2020-04-21 13:48:07 -04:00
trop[bot]
9bc01adbd5 spec: fix type errors in devToolsWebContents (#23189)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-04-20 20:22:35 -07:00
Electron Bot
af43d065d2 chore: bump chromium to 83.0.4103.20 (9-x-y) (#23155)
* chore: bump chromium in DEPS to 83.0.4103.17

* chore: bump chromium in DEPS to 83.0.4103.18

* chore: bump chromium in DEPS to 83.0.4103.19

* chore: bump chromium in DEPS to 83.0.4103.20
2020-04-20 12:02:42 -07:00
Shelley Vohr
6f4412a317 fix: wasm codegen in script.runInNewContext (#23146) 2020-04-20 15:01:30 -04:00
trop[bot]
f0a34a2f91 docs: fix devToolsWebContents union type (#23170)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-04-20 11:35:57 -07:00
Shelley Vohr
30e68263ed fix: print from PDF viewer not working (#23173) 2020-04-20 11:32:28 -07:00
Electron Bot
73a6d507a7 Bump v9.0.0-beta.18 2020-04-20 08:32:24 -07:00
trop[bot]
ff6e411973 fix: don't assign NSAlert to window which is not visible (#23090) 2020-04-16 20:25:47 -07:00
Electron Bot
fe5af34acc chore: bump chromium to 83.0.4103.16 (9-x-y) (#23121) 2020-04-16 14:17:12 -07:00
Electron Bot
cd359127c9 Bump v9.0.0-beta.17 2020-04-16 14:15:56 -07:00
Samuel Attard
248beeb7a5 Merge pull request from GHSA-h9jc-284h-533g 2020-04-16 14:10:18 -07:00
Samuel Attard
bb2773bf66 Revert "Bump v9.0.0-beta.17"
This reverts commit 27367fb553.
2020-04-16 14:06:24 -07:00
trop[bot]
914a8d02b4 docs: explain the swipe event on macOS (#23135)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-04-16 10:55:25 -07:00
Electron Bot
27367fb553 Bump v9.0.0-beta.17 2020-04-16 08:31:59 -07:00
Electron Bot
f193d9a34f chore: bump chromium to 83.0.4103.14 (9-x-y) (#23095)
Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-04-14 17:14:59 -07:00
trop[bot]
bcb7fbd9c5 build: improve patch filename remembering (#23092)
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-04-13 14:59:31 -07:00
Electron Bot
9762f6e7fd Bump v9.0.0-beta.16 2020-04-13 08:32:34 -07:00
trop[bot]
aef7986c64 fix: persist maximizable state when toggling fullscreen (#23021)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-04-10 09:40:26 -07:00
trop[bot]
82b65dcca7 fix: reset node env earlier during shutdown (#23068)
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-04-10 09:37:04 -07:00
Eryk Rakowski
afeda19d15 fix(extensions): add more properties to port.sender.tab (#23008) 2020-04-09 12:21:03 -07:00
Electron Bot
1c791f9056 Bump v9.0.0-beta.15 2020-04-09 08:33:07 -07:00
Jeremy Apthorp
359110a651 ci: auto-3way patches and detect changes (#23031)
* ci: auto-3way patches and detect changes (#22976)

* bust cache

* update-index || true

* update patches

* lint

* idk what is up with lint

* idek

* update patches

Co-authored-by: Electron Bot <anonymous@electronjs.org>
2020-04-09 11:06:57 -04:00
Jeremy Apthorp
7894ae9c01 chore: bump chromium to 83.0.4102.3 (#22941)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: Andy Locascio <andy@slack-corp.com>
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-04-08 14:37:31 -07:00
trop[bot]
ab41460616 fix: nullptr check when closing windows (#23023) 2020-04-07 22:10:28 -07:00
trop[bot]
8a4d41cd46 build: set merge=union for .patches (#22991)
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-04-07 09:55:17 -07:00
trop[bot]
41f33edd9e fix: webframe crashes for removed render frame (#22949)
* fix: webframe crashes for removed render frame

* Make errors more descriptive

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-04-06 11:38:44 -07:00
Electron Bot
21f78af43f Bump v9.0.0-beta.14 2020-04-06 08:32:24 -07:00
Electron Bot
c09bfb5a79 chore: bump chromium in DEPS to 82.0.4085.27 (#22932) 2020-04-02 17:53:33 -07:00
Electron Bot
fb5871ccf0 Bump v9.0.0-beta.13 2020-04-02 08:31:38 -07:00
Cheng Zhao
f255d47073 fix: webRequest module should work with file:// protocol (9-x-y) (#22919)
* fix: webRequest module should work with file:// protocol

* test: do not trigger unhandled promise rejections
2020-04-01 13:36:24 -07:00
trop[bot]
050844dc38 fix: screen module should still be creatable if the first create is before the ready event (#22912)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-04-01 11:37:12 -07:00
trop[bot]
317f1a3d3a fix: ensure standard schemes are registered in nw service process (#22917)
* fix: ensure standard schemes are registered in nw service process

Refs https://github.com/electron/electron/pull/20546

* chore: add test

* chore: apply suggestions from code review

Co-Authored-By: Jeremy Apthorp <jeremya@chromium.org>

Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: Jeremy Apthorp <jeremya@chromium.org>
2020-03-31 21:25:45 -07:00
trop[bot]
b4675ce0ae fix: dialog fails to show after modal close (#22890)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-03-31 14:50:11 -07:00
trop[bot]
3f6227c61a fix: propagate preferred color scheme to the renderer (#22900)
* fix: do not crash if the window is closed syncronously with a nativeTheme change

* fix: propogate preferred color scheme to the renderer and keep it up to date

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-03-31 14:46:15 -07:00
Electron Bot
bfc069e5c0 Bump v9.0.0-beta.12 2020-03-30 08:32:46 -07:00
trop[bot]
6cb495034e fix: don't try to hide host which has set flag disable_hidden_ (#22852)
Lack of this change will lead to freeze after call to hide/show
on window which has set flag disable_hidden_. To reproduce the
problem it's necessary to create some number of windows (how many
it depends on number of windows being cached by Chromium's
FrameEvictionManager).

Co-authored-by: Cezary Kulakowski <cezary@openfin.co>
2020-03-27 11:42:20 -07:00
trop[bot]
6d2cf47797 fix: heap-use-after-free in tray.popUpContextMenu (#22855)
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-03-27 15:34:18 +09:00
Electron Bot
e06880a90e Bump v9.0.0-beta.11 2020-03-26 08:32:00 -07:00
trop[bot]
0600420bac fix: don't allow window to go behind menu bar on mac (#22828)
Co-authored-by: Cezary Kulakowski <cezary@openfin.co>
2020-03-25 10:45:32 -05:00
trop[bot]
a6ff42c190 fix: workaround for hang when preventDefault-ing nativeWindowOpen (#22750)
* fix: enable workaround for nativeWindowOpen hang

* add test

* test: ensure window doesn't leak into other test

* update to use new webcontents delegate methods

Co-authored-by: Andy Locascio <andy@slack-corp.com>
2020-03-25 10:40:12 +09:00
trop[bot]
969579070f refactor: migrate base::ThreadPool() as trait to base::ThreadPool:: API (#22607) 2020-03-24 15:09:28 -07:00
trop[bot]
9ee656f856 build: fix missing pdf dep in chromium_src (#22814)
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-03-24 09:00:51 -04:00
Samuel Attard
7e4475cf51 Revert "fix: better window hierarchy checks"
This reverts commit 792fe833d1.
2020-03-23 19:35:21 -07:00
Samuel Attard
79d9bd3a29 Revert "Bump v9.0.0-beta.11"
This reverts commit 50debf8595.
2020-03-23 19:35:12 -07:00
Electron Bot
50debf8595 Bump v9.0.0-beta.11 2020-03-23 15:30:19 -07:00
Electron Bot
2d68a4d2ee Revert "Bump v9.0.0-beta.11"
This reverts commit 6ce0062c0e.
2020-03-23 15:25:54 -07:00
Electron Bot
6ce0062c0e Bump v9.0.0-beta.11 2020-03-23 14:27:00 -07:00
Samuel Attard
86aa0dfc45 build: fix beta version bumper logic for betas beyond 10 2020-03-23 14:25:54 -07:00
Samuel Attard
792fe833d1 fix: better window hierarchy checks 2020-03-23 14:13:00 -07:00
Samuel Attard
9b14ae770d feat: add support for net requests to use the session cookie store (#22806)
* chore: refactor all the net specs to be async with better error handling (#22731)

* chore: fix net specs when rerunning locally (#22745)

* feat: add support for net requests to use the session cookie store (#22704)

* fix: allow net requests to use Same-Site cookies (#22788)
2020-03-23 10:53:40 -07:00
Samuel Attard
5a34ad4e21 build: enable JS semicolons (#22785) 2020-03-23 09:18:28 -07:00
Eryk Rakowski
edd7e97dd9 feat(extensions): add more properties to extension object (#22595) 2020-03-21 10:28:05 +09:00
Electron Bot
b92734d912 chore: bump chromium to 82.0.4085.14 (9-x-y) (#22743) 2020-03-20 15:58:36 -07:00
Shelley Vohr
dc25ad2ef0 chore: update app module property support (#22747) 2020-03-20 16:09:47 +09:00
Charles Kerr
54a8258c1c refactor: omit duplicates from app's x11 icon list (#22736) 2020-03-19 14:12:07 -07:00
trop[bot]
02eff88e1b chore: revert deprecated WebContents properties (#22682) 2020-03-19 14:11:41 -07:00
trop[bot]
09ca564bf4 fix: prevent crash in ListValue v8 converter when conversion fails (#22759) 2020-03-19 14:02:31 -07:00
trop[bot]
54e31956f8 chore: more modules to dual prop/fn support (#22734) 2020-03-19 09:27:39 -07:00
Electron Bot
c6539f0d01 Bump v9.0.0-beta.10 2020-03-19 08:31:11 -07:00
trop[bot]
bef8448393 build: auto-generate the codesigning cert used for macOS CI testing runs (#22762)
* build: auto-generate the codesigning cert used for macOS CI testing runs

* build: give the cert ALL the trust values

* chore: also import public key

* idek

Co-authored-by: Samuel Attard <sattard@slack-corp.com>
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-03-19 16:34:57 +09:00
trop[bot]
27619e8ab0 test: no need to loadURL in menu test (#22765)
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2020-03-19 16:33:57 +09:00
trop[bot]
89c23f313f fix: crash when destroying WebContentsView during GC (#22764)
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-03-19 16:33:22 +09:00
Cheng Zhao
a9b9016b99 fix: avoid double-free in TrackableObject (#22768) 2020-03-19 16:32:46 +09:00
trop[bot]
f1c1542958 chore: support props/fns for BrowserWindow (#22733) 2020-03-18 19:53:11 -07:00
Сковорода Никита Андреевич
cb90ef47bb feat: add disableDialogs option to WebPreferences (#22664)
Allows to disable dialogs completely in a similar way of how safeDialogs option can be used. Overrides safeDialogs option.
2020-03-18 20:02:29 -04:00
trop[bot]
a345fe2b4f fix: persist maximizable state through theme change (#22724)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-03-18 09:39:00 +09:00
trop[bot]
0cf02dd78b fix: remove bad usages of for-in and guard against it (#22727)
* fix: remove bad usages of for-in and guard against it

* Apply suggestions from code review

Co-Authored-By: Samuel Maddock <samuel.maddock@gmail.com>

* Apply suggestions from code review

Co-Authored-By: Jeremy Apthorp <jeremya@chromium.org>

* Update remote.js

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: Samuel Attard <sattard@slack-corp.com>
Co-authored-by: Samuel Maddock <samuel.maddock@gmail.com>
Co-authored-by: Jeremy Apthorp <jeremya@chromium.org>
2020-03-18 09:38:26 +09:00
Electron Bot
3d941fc464 chore: bump chromium to 82.0.4085.10 (9-x-y) (#22506)
* chore: bump chromium in DEPS to 82.0.4076.1

* update patches

* chore: bump chromium in DEPS to 82.0.4077.1

* update v8 patches

* Remove deprecated wasm module type check

https://chromium-review.googlesource.com/c/v8/v8/+/2033170

* chore: bump chromium in DEPS to 82.0.4078.0

* chore: bump chromium in DEPS to 82.0.4079.0

* chore: bump chromium in DEPS to 82.0.4080.0

* chore: bump chromium in DEPS to 82.0.4080.1

* chore: bump chromium in DEPS to 82.0.4081.2

* Update patches

* chore: bump chromium in DEPS to 82.0.4082.1

* Remove cursor_types.h in favor of cursor_type.mojom

https://chromium-review.googlesource.com/c/chromium/src/+/2052103
(cherry picked from commit 3b6e4d4a1a)

* Refactor extensions report in management disclosure page

https://chromium-review.googlesource.com/c/chromium/src/+/2038774
(cherry picked from commit b2ae06307d)

* Rename an old referrer policy value

https://chromium-review.googlesource.com/c/chromium/src/+/2082856
(cherry picked from commit 3cb8af2515)

* Fixup compiler errors

* Move GLHelper to gpu::

https://chromium-review.googlesource.com/c/chromium/src/+/2023282
(cherry picked from commit ea8e347088)

* [api] Remove deprecated wasm module type check

https://chromium-review.googlesource.com/c/v8/v8/+/2033170
(cherry picked from commit 937988e6ce)

* Replace blink::WebCursorInfo with ui::Cursor

https://chromium-review.googlesource.com/c/chromium/src/+/1997138
(cherry picked from commit 3e348c4d59)

* DownloadURLParameters: Remove NetworkIsolationKey parameter.

https://chromium-review.googlesource.com/c/chromium/src/+/2050987
(cherry picked from commit 9b4aae745e)

* Convert FrameHostMsg_UpdateFaviconURL to mojo

https://chromium-review.googlesource.com/c/chromium/src/+/2043181
(cherry picked from commit 11b9c27eee)

* Merge ui::ContextFactoryPrivate with ui::ContextFactory

https://chromium-review.googlesource.com/c/chromium/src/+/2047728
(cherry picked from commit 176876f243)

* fix pdf viewer tests by binding more mojo things

(cherry picked from commit 74def418df)

* chore: bump chromium in DEPS to 82.0.4083.1

* Update electron_swiftshader_binaries deps

https://chromium-review.googlesource.com/c/chromium/src/+/2056931
(cherry picked from commit 312f11129f)

* Update patches

* Use Promise with RequestPointerLock calls

https://chromium-review.googlesource.com/c/chromium/src/+/2069199
(cherry picked from commit 34350db4bd)

* chore: bump chromium in DEPS to 82.0.4084.1

* Replace content::CursorInfo with ui::Cursor

https://chromium-review.googlesource.com/c/chromium/src/+/1999201
(cherry picked from commit 6b3b850692)

* Convert MaterialDesignController to a true singleton.

https://chromium-review.googlesource.com/c/chromium/src/+/2090877
(cherry picked from commit 21ced9206d)

* Drop WebContentsView::RenderViewCreated hook

https://chromium-review.googlesource.com/c/chromium/src/+/2093535
(cherry picked from commit 9ff4e65053)

* Update patches

* Splitting context_menu_params.h into separate browser VS common parts.

https://chromium-review.googlesource.com/c/chromium/src/+/2097468

* Fix DCHECK on OnThemeChanged()

https://chromium-review.googlesource.com/c/chromium/src/+/2090713
(cherry picked from commit fcec5f74d1)

* chore: bump chromium in DEPS to 82.0.4085.1

* chore: bump chromium in DEPS to 82.0.4085.5

* chore: bump chromium in DEPS to 82.0.4085.7

* chore: bump chromium in DEPS to 82.0.4085.9

* Add debugging for TAB tests

* update patches

* fix: add patch to fix linux arm build (#22523)


(cherry picked from commit 479354e721)

* chore: bump chromium in DEPS to 82.0.4085.10

* Check PointerLock requests for new options and update accordingly

https://chromium-review.googlesource.com/c/chromium/src/+/2071788

* Update for changes from master roller review

* FIXME: skip Menu.setApplicationMenu(null) test

(cherry picked from commit 305f167889)

* Revert "Add debugging for TAB tests"

This reverts commit af393c1b0b.

Co-authored-by: Jeremy Apthorp <jeremya@chromium.org>
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2020-03-17 16:56:25 -04:00
Erick Zhao
d32e1f8d97 fix: prevent in-memory sessions from writing to custom spellchecker dictionary (#22157) (#22683)
* fix: prevent in-memory sessions from writing to custom dictionary

* docs

* spec
2020-03-17 10:50:00 -05:00
Shelley Vohr
b798e1ff54 fix: crash on invalid zoomFactor (#22708) 2020-03-17 10:43:06 -05:00
Electron Bot
bcb1d529ff Bump v9.0.0-beta.9 2020-03-16 08:31:53 -07:00
trop[bot]
e217a9416a fix: when building with enable_pepper_flash = false (#22692)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2020-03-15 11:59:34 -07:00
Erick Zhao
4be52b8f78 test: add specs for custom dictionary API (#22681) 2020-03-13 14:53:37 -04:00
Electron Bot
2bc7aaf2ef Bump v9.0.0-beta.8 2020-03-12 08:32:27 -07:00
Erick Zhao
1114954cbf fix: guard against duplicate TouchBarItem IDs (#22644) 2020-03-12 10:48:17 +09:00
trop[bot]
a76ea622b9 build: fix broken Views build (#22642)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-03-11 16:00:55 +09:00
trop[bot]
7ee7890fd8 test: test setPath for errors thrown (#22639)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-03-10 10:03:36 -07:00
trop[bot]
851ed7a6e7 fix: when building with enable_pdf_viewer = false (#22631)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2020-03-10 10:54:07 -04:00
loc
16c4d6e487 fix: port CL that fixes ARIA tree impl for macOS (#22421) 2020-03-10 19:53:39 +09:00
trop[bot]
56c0ba138b chore: don't delete nightly tag after draft (#22624)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-03-10 17:04:34 +09:00
Alexey Kuzmin
1883edd869 build: fix build without built-in spellchecker (#22610)
(cherry picked from commit 385d778a8b4e97c1cffa8f31efcf9748e82e3d94)
2020-03-10 16:59:39 +09:00
trop[bot]
9b5f2159a2 build: upload sentry src bundles to symbol S3 bucket (#22617)
Co-authored-by: Samuel Attard <sattard@slack-corp.com>
2020-03-09 16:24:44 -07:00
Samuel Maddock
60f16eaf95 fix: disallow loading extensions in temp sessions (#22090) (#22571)
Co-authored-by: Jeremy Apthorp <jeremya@chromium.org>
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-03-09 11:33:23 -07:00
Electron Bot
0f08c6c874 Bump v9.0.0-beta.7 2020-03-09 08:31:47 -07:00
trop[bot]
e227578ae0 optional typically sync callback for WebFrame#executeJavaScript* (#22501)
Co-authored-by: bughit <bughit@users.noreply.github.com>
2020-03-06 19:07:46 +09:00
trop[bot]
f413cda758 feat(extensions): add chrome.tabs.connect API (#22549)
* feat(extensions): add chrome.tabs.connect API

* test(extensions): verify that chrome.tabs.connect port communication works

Co-authored-by: samuelmaddock <samuel.maddock@gmail.com>
2020-03-06 19:06:40 +09:00
trop[bot]
c41b543842 feat: add events for spellcheck dictionary downloads (#22556)
Co-authored-by: Samuel Attard <sattard@slack-corp.com>
2020-03-06 19:04:52 +09:00
trop[bot]
7bb430dc44 docs: clean up dark mode related docs (#22562)
* docs: clean up systemPreferences.effectiveAppearance text

* Grammar fixes
* Add links for Electron Packager & Electron Forge
* Update Packager API links, given https://github.com/electron/electron-packager/pull/1131

* docs: clean up Dark Mode guide

* Grammar fixes
* Add links for Electron Packager & Electron Forge

* docs: adjust based on Electron 8 using 10.14 SDK

Co-authored-by: Mark Lee <electronjs@lazymalevolence.com>
2020-03-06 19:04:31 +09:00
Samuel Maddock
231f5af1a1 feat(extensions): add chrome.i18n API (#22570)
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
Co-authored-by: Samuel Maddock <samuel.maddock@gmail.com>

Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-03-06 19:03:38 +09:00
Samuel Attard
539ca773de feat: programmatically modify traffic light positioning (#22533) (#22566)
* setter

* getter

* specs and docs

* fixup

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-03-05 16:59:00 -08:00
Samuel Attard
1fc197bedb fix: allow persistent media salts (#22386) (#22567)
* fix: allow persistent media salts

* chore: add regression test for persistent media device ids across reloads

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-03-05 16:58:39 -08:00
Samuel Attard
926bea232d fix: reposition traffic lights on theme change (#22560)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-03-05 14:02:23 -08:00
Electron Bot
db664f3433 Bump v9.0.0-beta.6 2020-03-05 07:31:44 -08:00
Cheng Zhao
9ec73a3dcf fix: destroy node platform after destroying wrappers (#22535)
Co-authored-by: Cheng Zhao <zcbenz@electronjs.org>
2020-03-05 13:07:48 +09:00
Samuel Attard
c1c1ac2b2e fix: disable contextBridge object identity caching (#22525)
* fix: disable contextBridge object identity caching

* cleanup

* chore: make non-const references raw pointers

* fix: zero-param constructors are not explicit

* refactor: use base::LinkedList

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-03-04 16:54:05 -08:00
trop[bot]
9c931136d0 fix: do not reposition traffic lights when fullscreened (#22509)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-03-04 20:08:43 +09:00
trop[bot]
f01ee72ea5 fix: properly forward properties to webview (#22511)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-03-04 20:08:19 +09:00
trop[bot]
f6b4c39195 fix: bail early if no printers on the network (#22519)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-03-04 16:19:37 +09:00
Cheng Zhao
578f59532d chore: remove unneeded header (#22520) 2020-03-04 12:31:46 +09:00
trop[bot]
dcf9e4b2dd docs: add documentation on case insensitive dictionary hosting (#22487)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-03-03 14:30:30 -08:00
Shelley Vohr
2021f25453 chore: ensure correct scopes are in place (#22479) 2020-03-03 11:30:52 +09:00
John Kleinschmidt
faee8a092d fix: revive offscreen rendering support (#22160) (#22425)
(cherry picked from commit 36f982aee2)

Co-authored-by: Andy Dill <andy@discordapp.com>
2020-03-02 20:17:48 -05:00
Erick Zhao
3d65d84193 feat: add session.removeWordFromSpellCheckerDictionary API (#22368)
* feat: add session.removeWordFromSpellCheckerDictionary API

* rebase fixup
2020-03-02 14:46:19 -08:00
Jeremy Apthorp
5e05df9f71 chore: update chromium to 82.0.4058.2 (#22198)
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: loc <andy@slack-corp.com>
Co-authored-by: Robo <hop2deep@gmail.com>
2020-03-02 11:21:22 -08:00
Electron Bot
df2d03fe9b Bump v9.0.0-beta.5 2020-03-02 07:31:48 -08:00
trop[bot]
80967287ad fix: make webRequest work for CORS preflight requests (#22468)
* fix: support CORS preflight

* test: webRequest should work for CORS requests

Co-authored-by: Cheng Zhao <zcbenz@electronjs.org>
2020-03-02 20:25:28 +09:00
trop[bot]
2855f1d237 fix: do not call close on sheets themselves (#22445)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-03-02 16:50:22 +09:00
trop[bot]
5e25649e77 fix: Add ContentsView to AXChildren (#22469)
Co-authored-by: Felix Rieseberg <felix@felixrieseberg.com>
2020-03-02 16:28:38 +09:00
trop[bot]
3e90e523eb fix: add patch to set the base download URL rather than override it completely (#22385)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-02-28 17:00:44 -08:00
trop[bot]
c702aec1f8 fix: dictionaries download path should be in userdata (#22447)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-02-28 17:00:15 -08:00
Electron Bot
ede40f260e Bump v9.0.0-beta.4 2020-02-27 16:37:59 -08:00
Samuel Attard
ca53cc8380 fix: backport v8 patch for type inference issue (#22426) 2020-02-27 12:48:23 -08:00
Samuel Attard
7ec9b4e252 feat: set app.enableRendererProcessReuse to true by default (#22336) (#22401)
* feat: set app.enableRendererProcessReuse to true by default

* chore: add context aware info to breaking changes doc

* spec: fix nodeIntegration in child windows test for rendererprocessreuse

* spec: fix remote listeners in destroyed renderers spec as the error is now async

* Update api-browser-window-spec.ts

* chore: deprecate affinity

* chore: fix docs

* spec: handle tests crashing without an exist code

* spec: update tests for new rendererprocessreuse default

* spec: with renderer process re-use we get to destroy less views
2020-02-27 12:19:31 -08:00
trop[bot]
0d7e13d2a6 feat: add API for receiving logs from service workers (#22313)
* feat: add API for receiving logs from service workers

* feat: add new serviceWorkerContext APIs

* chore: add missing #include's

* refactor: rename serviceWorkerContext to serviceWorkers

* chore: clean up based on review

* chore: remove native_mate

* chore: add tests for the service worker module

* Update spec-main/api-service-workers-spec.ts

Co-Authored-By: Jeremy Apthorp <jeremya@chromium.org>

* chore: fix linting

* chore: handle renames

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-02-27 11:02:31 -08:00
Electron Bot
a5c56684b9 Revert "Bump v9.0.0-beta.4"
This reverts commit 68d96459c3.
2020-02-27 10:01:45 -08:00
Electron Bot
68d96459c3 Bump v9.0.0-beta.4 2020-02-27 09:14:14 -08:00
trop[bot]
6cf4757019 docs: improve documentation on spellchecker download URL (#22402)
* docs: improve documentation on spellchecker download URL

* Update session.md

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-02-26 23:09:43 -08:00
trop[bot]
6e80d6fba5 fix: pass safeDialogs preference properly (#22378)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-02-26 13:34:42 -08:00
trop[bot]
ec07954d5d test: disable clipboard tests for WOA (#22387)
Co-authored-by: Cheng Zhao <zcbenz@github.com>
2020-02-26 18:04:32 +09:00
Electron Bot
7fd2d39f72 Bump v9.0.0-beta.3 2020-02-25 14:03:20 -08:00
trop[bot]
64880c75a3 fix: emit will-navigate for sandboxed contents (#22327)
* fix: emit will-navigate for sandboxed contents

* uncomment test

* more tests

* use ShouldFork instead of browser_handles_all_top_level_requests

* forward post data in OpenURLFromTab

* align OpenURLFromTab with chrome's version (browser_navigator.cc/LoadURLInContents)

* add tests for navigating from file: and about:blank to http:

Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-02-25 11:11:33 +09:00
trop[bot]
cc94689db1 chore: allow custom node-spec-runner options (#22331)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-02-25 11:10:02 +09:00
trop[bot]
806e483049 fix(extensions): set lowest isolated world id (#22355)
* fix(extensions): set lowest isolated world id

* refactor: move world IDs into separate header file

Several files are including electron_render_frame_observer.h just for the world IDs.

Co-authored-by: Samuel Maddock <samuel.maddock@gmail.com>
2020-02-25 11:01:30 +09:00
Jeremy Apthorp
8129e92d2b refactor: saner blink_initialization_order.patch (#22054) (#22366) 2020-02-24 13:19:33 -08:00
trop[bot]
660706ba2c fix: disable remote layer APIs in MAS build (#22354)
* fix: add patch to disable remote layer APIs

* fix: use --disable-gpu-memory-buffer-compositor-resources for MAS build

Co-authored-by: Cheng Zhao <zcbenz@github.com>
2020-02-24 19:29:15 +09:00
trop[bot]
2feca9d35a fix: revert {Atom => Electron}Application rename (#22325)
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-02-24 12:29:10 +09:00
trop[bot]
3a4118703a docs: clean up protocol docs (#22308)
* docs: clean up protocol docs

* Fix capitalization

Co-authored-by: Mark Lee <malept@users.noreply.github.com>
2020-02-24 12:28:29 +09:00
Jeremy Apthorp
bd669f72ee feat: [extensions] support extension.getBackgroundPage (#21951) (#22177) 2020-02-21 14:33:28 -08:00
trop[bot]
1f3c3eee83 fix: typo in crash reporter constructor (#22322) 2020-02-21 17:50:32 +00:00
trop[bot]
6e84ebee8e chore: remove libcc from release not generator (#22294)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-02-20 10:17:41 -05:00
trop[bot]
f22376ef32 fix: add patch to route mouse event navigations through the WebContentsDelegate (#22204)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-02-19 13:46:15 +09:00
trop[bot]
324b49a5eb doc: remove accidental deprecation (#22264)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-02-19 13:44:45 +09:00
John Kleinschmidt
0e9727e8d5 ci: enable goma for all testing builds (#21992) (#22203)
(cherry picked from commit e7982623ec)
2020-02-18 12:03:33 -05:00
trop[bot]
44ee90e5cf fix: don't include breakpad_symbols dir in dsym.zip (#22221)
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-02-18 11:57:33 -05:00
Electron Bot
2b9ef75d82 Bump v9.0.0-beta.2 2020-02-15 16:07:17 -08:00
John Kleinschmidt
4dc2b4d55f Revert "Bump v9.0.0-beta.2"
This reverts commit 69f77d309d.
2020-02-14 16:50:33 -05:00
Electron Bot
69f77d309d Bump v9.0.0-beta.2 2020-02-14 12:39:21 -08:00
John Kleinschmidt
9cd1744a2b Revert "Bump v9.0.0-beta.2"
This reverts commit 8b11adc6f2.
2020-02-14 15:37:16 -05:00
trop[bot]
c6e411173e build: fix release script to work with sudowoodo (#22200)
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-02-14 15:35:10 -05:00
Electron Bot
8b11adc6f2 Bump v9.0.0-beta.2 2020-02-14 12:29:44 -08:00
trop[bot]
57ec30e459 feat: add session.listWordsFromSpellCheckerDictionary API (#22128)
* doesn't work yet but compiles.

* works

* fixup

Co-authored-by: Erick Zhao <erick@hotmail.ca>
2020-02-14 15:26:17 -05:00
trop[bot]
c4a836f95a fix: crash on custom printing margins (#22185)
Co-authored-by: Shelley Vohr <codebytere@github.com>
2020-02-14 15:40:01 +09:00
trop[bot]
9b2de2583e fix: no-arg console.log is undefined (#22171)
Co-authored-by: Shelley Vohr <codebytere@github.com>
2020-02-14 15:31:49 +09:00
Samuel Attard
7b2bfb4a0f chore: update NMV for Electron 9 (#22189) 2020-02-13 13:18:22 -08:00
Jeremy Apthorp
0e31826043 feat: enable pdf viewer 2020-02-13 10:09:29 -08:00
trop[bot]
7b3c073b3e fix RTL bug when used with traffic light repositioning (#22163)
Co-authored-by: tonyfwoo <55114329+tonyfwoo@users.noreply.github.com>
2020-02-12 11:29:03 -06:00
trop[bot]
2dc900b95d ci: Speed up release (#22159) 2020-02-12 07:27:21 +00:00
trop[bot]
f41423501a fix: make webRequest work with WebSocket (#22133)
* fix: web request support proxying websocket

* fix: make tests work

* chore: do not use api:: code outside api/ folder

* chore: do not create proxy when no listener

* test: use separate session to avoid conflicts

* chore: address review

Co-authored-by: Cheng Zhao <zcbenz@github.com>
2020-02-11 15:56:00 -05:00
trop[bot]
07d9728b63 build: fix spellchecker deps (#22155)
Co-authored-by: Alexey Kuzmin <alex.s.kuzmin@gmail.com>
2020-02-11 15:45:46 -05:00
trop[bot]
d4c90e80a5 refactor: use NSVisualEffectMaterial* constants directly (#22148) 2020-02-11 17:48:44 +00:00
trop[bot]
91141028e6 chore: print more logging for failed tests (#22116)
Co-authored-by: Cheng Zhao <zcbenz@github.com>
2020-02-11 15:07:54 +09:00
Electron Bot
9d69d4b9ef Bump v9.0.0-beta.1 2020-02-10 06:50:08 -08:00
Electron Bot
d57d5c544e Revert "Bump v9.0.0-beta.2"
This reverts commit 74d4dab157.
2020-02-10 06:38:44 -08:00
Electron Bot
74d4dab157 Bump v9.0.0-beta.2 2020-02-10 04:32:41 -08:00
trop[bot]
94246dabe3 ci: fix build failure on doc only changes (#22089)
* ci: fix build failure on doc only changes

* ci: fix doc-only check when CI fires on branch before PR is created

Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-02-10 17:09:35 +09:00
trop[bot]
4fe91e56ac chore: remove debugging log (#22095)
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-02-10 17:05:34 +09:00
trop[bot]
0d7440d676 build: preserve timestamps when stripping files (#22098)
* build: preserve timestamps when stripping files

Resolves an issue where the binaries in mksnapshot.zip were not getting stripped.

* Add missing comma

* Update script/strip-binaries.py

Co-Authored-By: Jeremy Apthorp <jeremya@chromium.org>

* Don't try to run strip on macOS

Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
Co-authored-by: Jeremy Apthorp <nornagon@nornagon.net>
2020-02-10 17:05:04 +09:00
trop[bot]
60edd2d3b1 build: try using newer version of xcode/macOS sdk (#22106)
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-02-10 16:48:51 +09:00
trop[bot]
d4d06660f3 fix: flash plugin (#22111)
* fix: flash plugin

Fixes https://github.com/electron/electron/issues/20744

* cleanup

* fix linting issue

Co-authored-by: t57ser <seve@live.at>
2020-02-10 10:50:56 +09:00
trop[bot]
8d8f15121b fix: use a WeakPtr so we do not UAF the store in FunctionLifetimeMonitor (#22113)
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2020-02-10 10:47:23 +09:00
trop[bot]
1f8cb5144d build: copy chromedriver to correct location (#22092)
* build: copy chromedriver to correct location

* build: try to free up disk space for macos releases

* get verbose mode working on strip-binaries

* Only use separate chromedriver build arm/arm64

* fixup circleci config

Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-02-07 16:49:04 -05:00
trop[bot]
667ee359a1 test: get native unit tests running (#22086)
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-02-07 10:53:09 -05:00
trop[bot]
4b009159ba docs: clarify requirements for GOOGLE_API_KEY (#22071)
* docs: clarify requirements for GOOGLE_API_KEY

* Update docs/api/environment-variables.md

Co-Authored-By: Mark Lee <malept@users.noreply.github.com>

* Update docs/api/environment-variables.md

Co-Authored-By: Mark Lee <malept@users.noreply.github.com>

* Update docs/api/environment-variables.md

Co-Authored-By: Mark Lee <malept@users.noreply.github.com>

* update

Co-authored-by: Erick Zhao <erick@hotmail.ca>
Co-authored-by: Mark Lee <malept@users.noreply.github.com>
2020-02-06 15:25:28 -05:00
John Kleinschmidt
89f66bd00c Revert "Bump v9.0.0-beta.1"
This reverts commit 68346fec55.
2020-02-06 15:19:27 -05:00
trop[bot]
2d542c6028 build: copy chromedriver to proper directory for release (#22073)
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-02-06 15:07:46 -05:00
Electron Bot
68346fec55 Bump v9.0.0-beta.1 2020-02-05 13:46:21 -08:00
Samuel Attard
23f32ca9f3 chore: prepare for 9.0.0-beta.1 2020-02-05 13:44:23 -08:00
686 changed files with 31159 additions and 24558 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -6,9 +6,11 @@
"browser": true "browser": true
}, },
"rules": { "rules": {
"semi": ["error", "always"],
"no-var": "error", "no-var": "error",
"no-unused-vars": 0, "no-unused-vars": 0,
"no-global-assign": 0, "no-global-assign": 0,
"guard-for-in": 2,
"@typescript-eslint/no-unused-vars": ["error", { "@typescript-eslint/no-unused-vars": ["error", {
"vars": "all", "vars": "all",
"args": "after-used", "args": "after-used",

1
.gitattributes vendored
View File

@@ -1,3 +1,4 @@
# `git apply` and friends don't understand CRLF, even on windows. Force those # `git apply` and friends don't understand CRLF, even on windows. Force those
# files to be checked out with LF endings even if core.autocrlf is true. # files to be checked out with LF endings even if core.autocrlf is true.
*.patch text eol=lf *.patch text eol=lf
patches/**/.patches merge=union

View File

@@ -224,7 +224,6 @@ grit("resources") {
] ]
# Mojo manifest overlays are generated. # Mojo manifest overlays are generated.
source_is_generated = true
grit_flags = [ grit_flags = [
"-E", "-E",
"target_gen_dir=" + rebase_path(target_gen_dir, root_build_dir), "target_gen_dir=" + rebase_path(target_gen_dir, root_build_dir),
@@ -494,15 +493,21 @@ source_set("electron_lib") {
deps += [ deps += [
":libnotify_loader", ":libnotify_loader",
"//build/config/linux/gtk", "//build/config/linux/gtk",
"//chrome/browser/ui/gtk",
"//dbus", "//dbus",
"//device/bluetooth", "//device/bluetooth",
"//third_party/breakpad:client", "//third_party/breakpad:client",
"//ui/events/devices/x11", "//ui/events/devices/x11",
"//ui/events/platform/x11", "//ui/events/platform/x11",
"//ui/gtk",
"//ui/views/controls/webview", "//ui/views/controls/webview",
"//ui/wm", "//ui/wm",
] ]
if (use_x11) {
deps += [
"//ui/gfx/x",
"//ui/gtk:x",
]
}
configs += [ ":gio_unix" ] configs += [ ":gio_unix" ]
include_dirs += [ "//third_party/breakpad" ] include_dirs += [ "//third_party/breakpad" ]
configs += [ "//build/config/linux:x11" ] configs += [ "//build/config/linux:x11" ]
@@ -629,8 +634,6 @@ source_set("electron_lib") {
deps += [ "//components/printing/common:mojo_interfaces" ] deps += [ "//components/printing/common:mojo_interfaces" ]
} }
deps += [ "shell/common/extensions/api:extensions_features" ]
deps += [ "shell/common/extensions/api" ]
deps += [ deps += [
"//components/pref_registry", "//components/pref_registry",
"//components/user_prefs", "//components/user_prefs",
@@ -642,12 +645,22 @@ source_set("electron_lib") {
] ]
if (enable_electron_extensions) { if (enable_electron_extensions) {
sources += filenames.lib_sources_extensions sources += filenames.lib_sources_extensions
deps += [
"shell/browser/extensions/api:api_registration",
"shell/common/extensions/api",
"shell/common/extensions/api:extensions_features",
"//chrome/browser/resources:component_extension_resources",
"//components/zoom",
]
} }
if (enable_pdf) { if (enable_pdf) {
# Printing depends on some //pdf code, so it needs to be built even if the # Printing depends on some //pdf code, so it needs to be built even if the
# pdf viewer isn't enabled. # pdf viewer isn't enabled.
deps += [ "//pdf" ] deps += [
"//pdf",
"//pdf:features",
]
} }
if (enable_pdf_viewer) { if (enable_pdf_viewer) {
deps += [ deps += [
@@ -728,9 +741,14 @@ if (is_mac) {
sources = [ sources = [
"$root_out_dir/egl_intermediates/libswiftshader_libEGL.dylib", "$root_out_dir/egl_intermediates/libswiftshader_libEGL.dylib",
"$root_out_dir/egl_intermediates/libswiftshader_libGLESv2.dylib", "$root_out_dir/egl_intermediates/libswiftshader_libGLESv2.dylib",
"$root_out_dir/vk_intermediates/libvk_swiftshader.dylib",
"$root_out_dir/vk_intermediates/vk_swiftshader_icd.json",
] ]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ] outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:swiftshader_library_copy" ] public_deps = [
"//ui/gl:swiftshader_egl_library_copy",
"//ui/gl:swiftshader_vk_library_copy",
]
} }
} }
group("electron_angle_library") { group("electron_angle_library") {
@@ -783,6 +801,7 @@ if (is_mac) {
include_dirs = [ "." ] include_dirs = [ "." ]
sources = filenames.framework_sources sources = filenames.framework_sources
libs = []
if (enable_osr) { if (enable_osr) {
libs += [ "IOSurface.framework" ] libs += [ "IOSurface.framework" ]

4
DEPS
View File

@@ -12,7 +12,7 @@ gclient_gn_args = [
vars = { vars = {
'chromium_version': 'chromium_version':
'2102ff0fb03469ca5ff317a168e6ad99ca0f23f1', '83.0.4103.24',
'node_version': 'node_version':
'v12.14.1', 'v12.14.1',
'nan_version': 'nan_version':
@@ -155,3 +155,5 @@ hooks = [
recursedeps = [ recursedeps = [
'src', 'src',
] ]
# Touch DEPS again to bust cache

View File

@@ -1 +1 @@
9.0.0-nightly.20200205 9.0.0-beta.19

View File

@@ -118,19 +118,29 @@ build_script:
} }
} }
- ps: >- - ps: >-
if ($env:GN_CONFIG -ne 'release') {
if (Test-Path 'env:RAW_GOMA_AUTH') { if (Test-Path 'env:RAW_GOMA_AUTH') {
$env:GOMA_OAUTH2_CONFIG_FILE = "$pwd\.goma_oauth2_config" $env:GOMA_OAUTH2_CONFIG_FILE = "$pwd\.goma_oauth2_config"
$env:RAW_GOMA_AUTH | Set-Content $env:GOMA_OAUTH2_CONFIG_FILE $env:RAW_GOMA_AUTH | Set-Content $env:GOMA_OAUTH2_CONFIG_FILE
.\src\electron\script\start-goma.ps1 -gomaDir "$pwd\src\electron\external_binaries\goma" }
git clone https://github.com/electron/build-tools.git
cd build-tools
npm install
mkdir third_party
node -e "require('./src/utils/goma.js').downloadAndPrepare()"
$env:GN_GOMA_FILE = node -e "console.log(require('./src/utils/goma.js').gnFilePath)"
$env:LOCAL_GOMA_DIR = node -e "console.log(require('./src/utils/goma.js').dir)"
cd ..
.\src\electron\script\start-goma.ps1 -gomaDir $env:LOCAL_GOMA_DIR
} }
- cd src - cd src
- set BUILD_CONFIG_PATH=//electron/build/args/%GN_CONFIG%.gn - set BUILD_CONFIG_PATH=//electron/build/args/%GN_CONFIG%.gn
- if DEFINED RAW_GOMA_AUTH (gn gen out/Default "--args=import(\"%BUILD_CONFIG_PATH%\") import(\"//electron/build/args/goma.gn\") %GN_EXTRA_ARGS% ") else (gn gen out/Default "--args=import(\"%BUILD_CONFIG_PATH%\") %GN_EXTRA_ARGS% cc_wrapper=\"%SCCACHE_PATH%\"") - if DEFINED GN_GOMA_FILE (gn gen out/Default "--args=import(\"%BUILD_CONFIG_PATH%\") import(\"%GN_GOMA_FILE%\") %GN_EXTRA_ARGS% ") else (gn gen out/Default "--args=import(\"%BUILD_CONFIG_PATH%\") %GN_EXTRA_ARGS% cc_wrapper=\"%SCCACHE_PATH%\"")
- gn check out/Default //electron:electron_lib - gn check out/Default //electron:electron_lib
- gn check out/Default //electron:electron_app - gn check out/Default //electron:electron_app
- gn check out/Default //electron:manifests - gn check out/Default //electron:manifests
- gn check out/Default //electron/shell/common/api:mojo - gn check out/Default //electron/shell/common/api:mojo
- if DEFINED RAW_GOMA_AUTH (ninja -j 300 -C out/Default electron:electron_app) else (ninja -C out/Default electron:electron_app) - if DEFINED GN_GOMA_FILE (ninja -j 300 -C out/Default electron:electron_app) else (ninja -C out/Default electron:electron_app)
- if "%GN_CONFIG%"=="testing" ( python C:\depot_tools\post_build_ninja_summary.py -C out\Default ) - if "%GN_CONFIG%"=="testing" ( python C:\depot_tools\post_build_ninja_summary.py -C out\Default )
- gn gen out/ffmpeg "--args=import(\"//electron/build/args/ffmpeg.gn\") %GN_EXTRA_ARGS%" - gn gen out/ffmpeg "--args=import(\"//electron/build/args/ffmpeg.gn\") %GN_EXTRA_ARGS%"
- ninja -C out/ffmpeg electron:electron_ffmpeg_zip - ninja -C out/ffmpeg electron:electron_ffmpeg_zip
@@ -140,7 +150,7 @@ build_script:
- ninja -C out/Default electron:hunspell_dictionaries_zip - ninja -C out/Default electron:hunspell_dictionaries_zip
- ninja -C out/Default electron:electron_chromedriver_zip - ninja -C out/Default electron:electron_chromedriver_zip
- ninja -C out/Default third_party/electron_node:headers - ninja -C out/Default third_party/electron_node:headers
- if DEFINED RAW_GOMA_AUTH (python electron\external_binaries\goma\goma_ctl.py stat) - if "%GN_CONFIG%"=="testing" ( python %LOCAL_GOMA_DIR%\goma_ctl.py stat )
- python electron/build/profile_toolchain.py --output-json=out/Default/windows_toolchain_profile.json - python electron/build/profile_toolchain.py --output-json=out/Default/windows_toolchain_profile.json
- appveyor PushArtifact out/Default/windows_toolchain_profile.json - appveyor PushArtifact out/Default/windows_toolchain_profile.json
- appveyor PushArtifact out/Default/dist.zip - appveyor PushArtifact out/Default/dist.zip

View File

@@ -2,7 +2,7 @@ is_electron_build = true
root_extra_deps = [ "//electron" ] root_extra_deps = [ "//electron" ]
# Registry of NMVs --> https://github.com/nodejs/node/blob/master/doc/abi_version_registry.json # Registry of NMVs --> https://github.com/nodejs/node/blob/master/doc/abi_version_registry.json
node_module_version = 76 node_module_version = 80
v8_promise_internal_field_count = 1 v8_promise_internal_field_count = 1
v8_typed_array_max_size_in_heap = 0 v8_typed_array_max_size_in_heap = 0
@@ -21,7 +21,6 @@ dawn_enable_vulkan_validation_layers = false
is_cfi = false is_cfi = false
# TODO: disabled due to crashes. re-enable. enable_osr = true
enable_osr = false
enable_electron_extensions = true enable_electron_extensions = true

View File

@@ -14,7 +14,7 @@ declare_args() {
enable_view_api = false enable_view_api = false
enable_pdf_viewer = false enable_pdf_viewer = true
enable_tts = true enable_tts = true

View File

@@ -51,8 +51,8 @@ static_library("chrome") {
"//chrome/browser/ssl/security_state_tab_helper.cc", "//chrome/browser/ssl/security_state_tab_helper.cc",
"//chrome/browser/ssl/security_state_tab_helper.h", "//chrome/browser/ssl/security_state_tab_helper.h",
"//chrome/browser/ssl/tls_deprecation_config.cc", "//chrome/browser/ssl/tls_deprecation_config.cc",
"//chrome/browser/ui/autofill/popup_view_common.cc", "//chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc",
"//chrome/browser/ui/autofill/popup_view_common.h", "//chrome/browser/ui/views/autofill/autofill_popup_view_utils.h",
"//chrome/browser/win/chrome_process_finder.cc", "//chrome/browser/win/chrome_process_finder.cc",
"//chrome/browser/win/chrome_process_finder.h", "//chrome/browser/win/chrome_process_finder.h",
"//extensions/browser/app_window/size_constraints.cc", "//extensions/browser/app_window/size_constraints.cc",
@@ -68,8 +68,8 @@ static_library("chrome") {
] ]
deps = [ deps = [
"//chrome/browser:resource_prefetch_predictor_proto", "//chrome/browser:resource_prefetch_predictor_proto",
"//chrome/browser/ssl:proto",
"//components/feature_engagement:buildflags", "//components/feature_engagement:buildflags",
"//components/optimization_guide/proto:optimization_guide_proto",
] ]
if (is_linux) { if (is_linux) {
@@ -110,11 +110,15 @@ static_library("chrome") {
] ]
if (use_aura) { if (use_aura) {
sources += [ "//chrome/browser/platform_util_aura.cc" ]
if (!is_win) {
sources += [ sources += [
"//chrome/browser/platform_util_aura.cc",
"//chrome/browser/ui/views/color_chooser_aura.cc", "//chrome/browser/ui/views/color_chooser_aura.cc",
"//chrome/browser/ui/views/color_chooser_aura.h", "//chrome/browser/ui/views/color_chooser_aura.h",
] ]
}
deps += [ "//components/feature_engagement" ] deps += [ "//components/feature_engagement" ]
} }
@@ -233,10 +237,18 @@ static_library("chrome") {
if (enable_electron_extensions) { if (enable_electron_extensions) {
sources += [ sources += [
"//chrome/browser/extensions/chrome_url_request_util.cc",
"//chrome/browser/extensions/chrome_url_request_util.h",
"//chrome/browser/pdf/pdf_extension_util.cc",
"//chrome/browser/pdf/pdf_extension_util.h",
"//chrome/browser/plugins/plugin_response_interceptor_url_loader_throttle.cc",
"//chrome/browser/plugins/plugin_response_interceptor_url_loader_throttle.h",
"//chrome/renderer/extensions/extension_hooks_delegate.cc", "//chrome/renderer/extensions/extension_hooks_delegate.cc",
"//chrome/renderer/extensions/extension_hooks_delegate.h", "//chrome/renderer/extensions/extension_hooks_delegate.h",
"//chrome/renderer/extensions/tabs_hooks_delegate.cc", "//chrome/renderer/extensions/tabs_hooks_delegate.cc",
"//chrome/renderer/extensions/tabs_hooks_delegate.h", "//chrome/renderer/extensions/tabs_hooks_delegate.h",
"//chrome/renderer/pepper/chrome_pdf_print_client.cc",
"//chrome/renderer/pepper/chrome_pdf_print_client.h",
] ]
} }
} }
@@ -256,6 +268,7 @@ source_set("plugins") {
"//chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.h", "//chrome/browser/renderer_host/pepper/pepper_isolated_file_system_message_filter.h",
] ]
deps += [ deps += [
"//components/pdf/browser",
"//media:media_buildflags", "//media:media_buildflags",
"//ppapi/buildflags", "//ppapi/buildflags",
"//ppapi/proxy:ipc", "//ppapi/proxy:ipc",
@@ -293,8 +306,6 @@ source_set("plugins") {
sources += [ sources += [
"//chrome/renderer/pepper/pepper_flash_drm_renderer_host.cc", "//chrome/renderer/pepper/pepper_flash_drm_renderer_host.cc",
"//chrome/renderer/pepper/pepper_flash_drm_renderer_host.h", "//chrome/renderer/pepper/pepper_flash_drm_renderer_host.h",
"//chrome/renderer/pepper/pepper_flash_font_file_host.cc",
"//chrome/renderer/pepper/pepper_flash_font_file_host.h",
"//chrome/renderer/pepper/pepper_flash_fullscreen_host.cc", "//chrome/renderer/pepper/pepper_flash_fullscreen_host.cc",
"//chrome/renderer/pepper/pepper_flash_fullscreen_host.h", "//chrome/renderer/pepper/pepper_flash_fullscreen_host.h",
"//chrome/renderer/pepper/pepper_flash_menu_host.cc", "//chrome/renderer/pepper/pepper_flash_menu_host.cc",
@@ -303,7 +314,14 @@ source_set("plugins") {
"//chrome/renderer/pepper/pepper_flash_renderer_host.h", "//chrome/renderer/pepper/pepper_flash_renderer_host.h",
] ]
} }
if (enable_pepper_flash || enable_pdf_viewer) {
sources += [
"//chrome/renderer/pepper/pepper_flash_font_file_host.cc",
"//chrome/renderer/pepper/pepper_flash_font_file_host.h",
]
}
deps += [ deps += [
"//components/pdf/renderer",
"//components/strings", "//components/strings",
"//media:media_buildflags", "//media:media_buildflags",
"//ppapi/host", "//ppapi/host",

View File

@@ -1,48 +1,48 @@
import { app, dialog, BrowserWindow, shell, ipcMain } from 'electron' import { app, dialog, BrowserWindow, shell, ipcMain } from 'electron';
import * as path from 'path' import * as path from 'path';
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null;
// Quit when all windows are closed. // Quit when all windows are closed.
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
app.quit() app.quit();
}) });
function decorateURL (url: string) { function decorateURL (url: string) {
// safely add `?utm_source=default_app // safely add `?utm_source=default_app
const parsedUrl = new URL(url) const parsedUrl = new URL(url);
parsedUrl.searchParams.append('utm_source', 'default_app') parsedUrl.searchParams.append('utm_source', 'default_app');
return parsedUrl.toString() return parsedUrl.toString();
} }
// Find the shortest path to the electron binary // Find the shortest path to the electron binary
const absoluteElectronPath = process.execPath const absoluteElectronPath = process.execPath;
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath) const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath);
const electronPath = absoluteElectronPath.length < relativeElectronPath.length const electronPath = absoluteElectronPath.length < relativeElectronPath.length
? absoluteElectronPath ? absoluteElectronPath
: relativeElectronPath : relativeElectronPath;
const indexPath = path.resolve(app.getAppPath(), 'index.html') const indexPath = path.resolve(app.getAppPath(), 'index.html');
function isTrustedSender (webContents: Electron.WebContents) { function isTrustedSender (webContents: Electron.WebContents) {
if (webContents !== (mainWindow && mainWindow.webContents)) { if (webContents !== (mainWindow && mainWindow.webContents)) {
return false return false;
} }
const parsedUrl = new URL(webContents.getURL()) const parsedUrl = new URL(webContents.getURL());
const urlPath = process.platform === 'win32' const urlPath = process.platform === 'win32'
// Strip the prefixed "/" that occurs on windows // Strip the prefixed "/" that occurs on windows
? path.resolve(parsedUrl.pathname.substr(1)) ? path.resolve(parsedUrl.pathname.substr(1))
: parsedUrl.pathname : parsedUrl.pathname;
return parsedUrl.protocol === 'file:' && urlPath === indexPath return parsedUrl.protocol === 'file:' && urlPath === indexPath;
} }
ipcMain.handle('bootstrap', (event) => { ipcMain.handle('bootstrap', (event) => {
return isTrustedSender(event.sender) ? electronPath : null return isTrustedSender(event.sender) ? electronPath : null;
}) });
async function createWindow () { async function createWindow () {
await app.whenReady() await app.whenReady();
const options: Electron.BrowserWindowConstructorOptions = { const options: Electron.BrowserWindowConstructorOptions = {
width: 900, width: 900,
@@ -57,46 +57,46 @@ async function createWindow () {
}, },
useContentSize: true, useContentSize: true,
show: false show: false
} };
if (process.platform === 'linux') { if (process.platform === 'linux') {
options.icon = path.join(__dirname, 'icon.png') options.icon = path.join(__dirname, 'icon.png');
} }
mainWindow = new BrowserWindow(options) mainWindow = new BrowserWindow(options);
mainWindow.on('ready-to-show', () => mainWindow!.show()) mainWindow.on('ready-to-show', () => mainWindow!.show());
mainWindow.webContents.on('new-window', (event, url) => { mainWindow.webContents.on('new-window', (event, url) => {
event.preventDefault() event.preventDefault();
shell.openExternal(decorateURL(url)) shell.openExternal(decorateURL(url));
}) });
mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, done) => { mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, done) => {
const parsedUrl = new URL(webContents.getURL()) const parsedUrl = new URL(webContents.getURL());
const options: Electron.MessageBoxOptions = { const options: Electron.MessageBoxOptions = {
title: 'Permission Request', title: 'Permission Request',
message: `Allow '${parsedUrl.origin}' to access '${permission}'?`, message: `Allow '${parsedUrl.origin}' to access '${permission}'?`,
buttons: ['OK', 'Cancel'], buttons: ['OK', 'Cancel'],
cancelId: 1 cancelId: 1
} };
dialog.showMessageBox(mainWindow!, options).then(({ response }) => { dialog.showMessageBox(mainWindow!, options).then(({ response }) => {
done(response === 0) done(response === 0);
}) });
}) });
return mainWindow return mainWindow;
} }
export const loadURL = async (appUrl: string) => { export const loadURL = async (appUrl: string) => {
mainWindow = await createWindow() mainWindow = await createWindow();
mainWindow.loadURL(appUrl) mainWindow.loadURL(appUrl);
mainWindow.focus() mainWindow.focus();
} };
export const loadFile = async (appPath: string) => { export const loadFile = async (appPath: string) => {
mainWindow = await createWindow() mainWindow = await createWindow();
mainWindow.loadFile(appPath) mainWindow.loadFile(appPath);
mainWindow.focus() mainWindow.focus();
} };

View File

@@ -1,8 +1,8 @@
import { app, dialog } from 'electron' import { app, dialog } from 'electron';
import * as fs from 'fs' import * as fs from 'fs';
import * as path from 'path' import * as path from 'path';
import * as url from 'url' import * as url from 'url';
type DefaultAppOptions = { type DefaultAppOptions = {
file: null | string; file: null | string;
@@ -14,10 +14,10 @@ type DefaultAppOptions = {
modules: string[]; modules: string[];
} }
const Module = require('module') const Module = require('module');
// Parse command line options. // Parse command line options.
const argv = process.argv.slice(1) const argv = process.argv.slice(1);
const option: DefaultAppOptions = { const option: DefaultAppOptions = {
file: null, file: null,
@@ -27,50 +27,50 @@ const option: DefaultAppOptions = {
interactive: false, interactive: false,
abi: false, abi: false,
modules: [] modules: []
} };
let nextArgIsRequire = false let nextArgIsRequire = false;
for (const arg of argv) { for (const arg of argv) {
if (nextArgIsRequire) { if (nextArgIsRequire) {
option.modules.push(arg) option.modules.push(arg);
nextArgIsRequire = false nextArgIsRequire = false;
continue continue;
} else if (arg === '--version' || arg === '-v') { } else if (arg === '--version' || arg === '-v') {
option.version = true option.version = true;
break break;
} else if (arg.match(/^--app=/)) { } else if (arg.match(/^--app=/)) {
option.file = arg.split('=')[1] option.file = arg.split('=')[1];
break break;
} else if (arg === '--interactive' || arg === '-i' || arg === '-repl') { } else if (arg === '--interactive' || arg === '-i' || arg === '-repl') {
option.interactive = true option.interactive = true;
} else if (arg === '--test-type=webdriver') { } else if (arg === '--test-type=webdriver') {
option.webdriver = true option.webdriver = true;
} else if (arg === '--require' || arg === '-r') { } else if (arg === '--require' || arg === '-r') {
nextArgIsRequire = true nextArgIsRequire = true;
continue continue;
} else if (arg === '--abi' || arg === '-a') { } else if (arg === '--abi' || arg === '-a') {
option.abi = true option.abi = true;
continue continue;
} else if (arg === '--no-help') { } else if (arg === '--no-help') {
option.noHelp = true option.noHelp = true;
continue continue;
} else if (arg[0] === '-') { } else if (arg[0] === '-') {
continue continue;
} else { } else {
option.file = arg option.file = arg;
break break;
} }
} }
if (nextArgIsRequire) { if (nextArgIsRequire) {
console.error('Invalid Usage: --require [file]\n\n"file" is required') console.error('Invalid Usage: --require [file]\n\n"file" is required');
process.exit(1) process.exit(1);
} }
// Set up preload modules // Set up preload modules
if (option.modules.length > 0) { if (option.modules.length > 0) {
Module._preloadModules(option.modules) Module._preloadModules(option.modules);
} }
function loadApplicationPackage (packagePath: string) { function loadApplicationPackage (packagePath: string) {
@@ -79,102 +79,102 @@ function loadApplicationPackage (packagePath: string) {
configurable: false, configurable: false,
enumerable: true, enumerable: true,
value: true value: true
}) });
try { try {
// Override app name and version. // Override app name and version.
packagePath = path.resolve(packagePath) packagePath = path.resolve(packagePath);
const packageJsonPath = path.join(packagePath, 'package.json') const packageJsonPath = path.join(packagePath, 'package.json');
let appPath let appPath;
if (fs.existsSync(packageJsonPath)) { if (fs.existsSync(packageJsonPath)) {
let packageJson let packageJson;
try { try {
packageJson = require(packageJsonPath) packageJson = require(packageJsonPath);
} catch (e) { } catch (e) {
showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${e.message}`) showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${e.message}`);
return return;
} }
if (packageJson.version) { if (packageJson.version) {
app.setVersion(packageJson.version) app.setVersion(packageJson.version);
} }
if (packageJson.productName) { if (packageJson.productName) {
app.name = packageJson.productName app.name = packageJson.productName;
} else if (packageJson.name) { } else if (packageJson.name) {
app.name = packageJson.name app.name = packageJson.name;
} }
appPath = packagePath appPath = packagePath;
} }
try { try {
const filePath = Module._resolveFilename(packagePath, module, true) const filePath = Module._resolveFilename(packagePath, module, true);
app._setDefaultAppPaths(appPath || path.dirname(filePath)) app._setDefaultAppPaths(appPath || path.dirname(filePath));
} catch (e) { } catch (e) {
showErrorMessage(`Unable to find Electron app at ${packagePath}\n\n${e.message}`) showErrorMessage(`Unable to find Electron app at ${packagePath}\n\n${e.message}`);
return return;
} }
// Run the app. // Run the app.
Module._load(packagePath, module, true) Module._load(packagePath, module, true);
} catch (e) { } catch (e) {
console.error('App threw an error during load') console.error('App threw an error during load');
console.error(e.stack || e) console.error(e.stack || e);
throw e throw e;
} }
} }
function showErrorMessage (message: string) { function showErrorMessage (message: string) {
app.focus() app.focus();
dialog.showErrorBox('Error launching app', message) dialog.showErrorBox('Error launching app', message);
process.exit(1) process.exit(1);
} }
async function loadApplicationByURL (appUrl: string) { async function loadApplicationByURL (appUrl: string) {
const { loadURL } = await import('./default_app') const { loadURL } = await import('./default_app');
loadURL(appUrl) loadURL(appUrl);
} }
async function loadApplicationByFile (appPath: string) { async function loadApplicationByFile (appPath: string) {
const { loadFile } = await import('./default_app') const { loadFile } = await import('./default_app');
loadFile(appPath) loadFile(appPath);
} }
function startRepl () { function startRepl () {
if (process.platform === 'win32') { if (process.platform === 'win32') {
console.error('Electron REPL not currently supported on Windows') console.error('Electron REPL not currently supported on Windows');
process.exit(1) process.exit(1);
} }
// prevent quitting // prevent quitting
app.on('window-all-closed', () => {}) app.on('window-all-closed', () => {});
const repl = require('repl') const repl = require('repl');
repl.start('> ').on('exit', () => { repl.start('> ').on('exit', () => {
process.exit(0) process.exit(0);
}) });
} }
// Start the specified app if there is one specified in command line, otherwise // Start the specified app if there is one specified in command line, otherwise
// start the default app. // start the default app.
if (option.file && !option.webdriver) { if (option.file && !option.webdriver) {
const file = option.file const file = option.file;
const protocol = url.parse(file).protocol const protocol = url.parse(file).protocol;
const extension = path.extname(file) const extension = path.extname(file);
if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:' || protocol === 'chrome:') { if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:' || protocol === 'chrome:') {
loadApplicationByURL(file) loadApplicationByURL(file);
} else if (extension === '.html' || extension === '.htm') { } else if (extension === '.html' || extension === '.htm') {
loadApplicationByFile(path.resolve(file)) loadApplicationByFile(path.resolve(file));
} else { } else {
loadApplicationPackage(file) loadApplicationPackage(file);
} }
} else if (option.version) { } else if (option.version) {
console.log('v' + process.versions.electron) console.log('v' + process.versions.electron);
process.exit(0) process.exit(0);
} else if (option.abi) { } else if (option.abi) {
console.log(process.versions.modules) console.log(process.versions.modules);
process.exit(0) process.exit(0);
} else if (option.interactive) { } else if (option.interactive) {
startRepl() startRepl();
} else { } else {
if (!option.noHelp) { if (!option.noHelp) {
const welcomeMessage = ` const welcomeMessage = `
@@ -192,10 +192,10 @@ Options:
-i, --interactive Open a REPL to the main process. -i, --interactive Open a REPL to the main process.
-r, --require Module to preload (option can be repeated). -r, --require Module to preload (option can be repeated).
-v, --version Print the version. -v, --version Print the version.
-a, --abi Print the Node ABI version.` -a, --abi Print the Node ABI version.`;
console.log(welcomeMessage) console.log(welcomeMessage);
} }
loadApplicationByFile('index.html') loadApplicationByFile('index.html');
} }

View File

@@ -1,53 +1,53 @@
import { ipcRenderer, contextBridge } from 'electron' import { ipcRenderer, contextBridge } from 'electron';
async function getOcticonSvg (name: string) { async function getOcticonSvg (name: string) {
try { try {
const response = await fetch(`octicon/${name}.svg`) const response = await fetch(`octicon/${name}.svg`);
const div = document.createElement('div') const div = document.createElement('div');
div.innerHTML = await response.text() div.innerHTML = await response.text();
return div return div;
} catch { } catch {
return null return null;
} }
} }
async function loadSVG (element: HTMLSpanElement) { async function loadSVG (element: HTMLSpanElement) {
for (const cssClass of element.classList) { for (const cssClass of element.classList) {
if (cssClass.startsWith('octicon-')) { if (cssClass.startsWith('octicon-')) {
const icon = await getOcticonSvg(cssClass.substr(8)) const icon = await getOcticonSvg(cssClass.substr(8));
if (icon) { if (icon) {
for (const elemClass of element.classList) { for (const elemClass of element.classList) {
icon.classList.add(elemClass) icon.classList.add(elemClass);
} }
element.before(icon) element.before(icon);
element.remove() element.remove();
break break;
} }
} }
} }
} }
async function initialize () { async function initialize () {
const electronPath = await ipcRenderer.invoke('bootstrap') const electronPath = await ipcRenderer.invoke('bootstrap');
function replaceText (selector: string, text: string) { function replaceText (selector: string, text: string) {
const element = document.querySelector<HTMLElement>(selector) const element = document.querySelector<HTMLElement>(selector);
if (element) { if (element) {
element.innerText = text element.innerText = text;
} }
} }
replaceText('.electron-version', `Electron v${process.versions.electron}`) replaceText('.electron-version', `Electron v${process.versions.electron}`);
replaceText('.chrome-version', `Chromium v${process.versions.chrome}`) replaceText('.chrome-version', `Chromium v${process.versions.chrome}`);
replaceText('.node-version', `Node v${process.versions.node}`) replaceText('.node-version', `Node v${process.versions.node}`);
replaceText('.v8-version', `v8 v${process.versions.v8}`) replaceText('.v8-version', `v8 v${process.versions.v8}`);
replaceText('.command-example', `${electronPath} path-to-app`) replaceText('.command-example', `${electronPath} path-to-app`);
for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) { for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) {
loadSVG(element) loadSVG(element);
} }
} }
contextBridge.exposeInMainWorld('electronDefaultApp', { contextBridge.exposeInMainWorld('electronDefaultApp', {
initialize initialize
}) });

View File

@@ -659,8 +659,6 @@ to the npm modules spec. You should usually also specify a `productName`
field, which is your application's full capitalized name, and which will be field, which is your application's full capitalized name, and which will be
preferred over `name` by Electron. preferred over `name` by Electron.
**[Deprecated](modernization/property-updates.md)**
### `app.setName(name)` ### `app.setName(name)`
* `name` String * `name` String
@@ -669,8 +667,6 @@ Overrides the current application's name.
**Note:** This function overrides the name used internally by Electron; it does not affect the name that the OS uses. **Note:** This function overrides the name used internally by Electron; it does not affect the name that the OS uses.
**[Deprecated](modernization/property-updates.md)**
### `app.getLocale()` ### `app.getLocale()`
Returns `String` - The current application locale. Possible return values are documented [here](locales.md). Returns `String` - The current application locale. Possible return values are documented [here](locales.md).
@@ -703,34 +699,34 @@ Clears the recent documents list.
### `app.setAsDefaultProtocolClient(protocol[, path, args])` ### `app.setAsDefaultProtocolClient(protocol[, path, args])`
* `protocol` String - The name of your protocol, without `://`. If you want your * `protocol` String - The name of your protocol, without `://`. For example,
app to handle `electron://` links, call this method with `electron` as the if you want your app to handle `electron://` links, call this method with
parameter. `electron` as the parameter.
* `path` String (optional) _Windows_ - Defaults to `process.execPath` * `path` String (optional) _Windows_ - The path to the Electron executable.
* `args` String[] (optional) _Windows_ - Defaults to an empty array Defaults to `process.execPath`
* `args` String[] (optional) _Windows_ - Arguments passed to the executable.
Defaults to an empty array
Returns `Boolean` - Whether the call succeeded. Returns `Boolean` - Whether the call succeeded.
This method sets the current executable as the default handler for a protocol Sets the current executable as the default handler for a protocol (aka URI
(aka URI scheme). It allows you to integrate your app deeper into the operating scheme). It allows you to integrate your app deeper into the operating system.
system. Once registered, all links with `your-protocol://` will be opened with Once registered, all links with `your-protocol://` will be opened with the
the current executable. The whole link, including protocol, will be passed to current executable. The whole link, including protocol, will be passed to your
your application as a parameter. application as a parameter.
On Windows, you can provide optional parameters path, the path to your executable,
and args, an array of arguments to be passed to your executable when it launches.
**Note:** On macOS, you can only register protocols that have been added to **Note:** On macOS, you can only register protocols that have been added to
your app's `info.plist`, which can not be modified at runtime. You can however your app's `info.plist`, which cannot be modified at runtime. However, you can
change the file with a simple text editor or script during build time. change the file during build time via [Electron Forge][electron-forge],
Please refer to [Apple's documentation][CFBundleURLTypes] for details. [Electron Packager][electron-packager], or by editing `info.plist` with a text
editor. Please refer to [Apple's documentation][CFBundleURLTypes] for details.
**Note:** In a Windows Store environment (when packaged as an `appx`) this API **Note:** In a Windows Store environment (when packaged as an `appx`) this API
will return `true` for all calls but the registry key it sets won't be accessible will return `true` for all calls but the registry key it sets won't be accessible
by other applications. In order to register your Windows Store application by other applications. In order to register your Windows Store application
as a default protocol handler you must [declare the protocol in your manifest](https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-uap-protocol). as a default protocol handler you must [declare the protocol in your manifest](https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-uap-protocol).
The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally. The API uses the Windows Registry and `LSSetDefaultHandlerForURLScheme` internally.
### `app.removeAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ ### `app.removeAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_
@@ -749,10 +745,8 @@ protocol (aka URI scheme). If so, it will remove the app as the default handler.
* `path` String (optional) _Windows_ - Defaults to `process.execPath` * `path` String (optional) _Windows_ - Defaults to `process.execPath`
* `args` String[] (optional) _Windows_ - Defaults to an empty array * `args` String[] (optional) _Windows_ - Defaults to an empty array
Returns `Boolean` Returns `Boolean` - Whether the current executable is the default handler for a
protocol (aka URI scheme).
This method checks if the current executable is the default handler for a protocol
(aka URI scheme). If so, it will return true. Otherwise, it will return false.
**Note:** On macOS, you can use this method to check if the app has been **Note:** On macOS, you can use this method to check if the app has been
registered as the default protocol handler for a protocol. You can also verify registered as the default protocol handler for a protocol. You can also verify
@@ -760,7 +754,7 @@ this by checking `~/Library/Preferences/com.apple.LaunchServices.plist` on the
macOS machine. Please refer to macOS machine. Please refer to
[Apple's documentation][LSCopyDefaultHandlerForURLScheme] for details. [Apple's documentation][LSCopyDefaultHandlerForURLScheme] for details.
The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally. The API uses the Windows Registry and `LSCopyDefaultHandlerForURLScheme` internally.
### `app.getApplicationNameForProtocol(url)` ### `app.getApplicationNameForProtocol(url)`
@@ -1091,14 +1085,10 @@ On macOS, it shows on the dock icon. On Linux, it only works for Unity launcher.
**Note:** Unity launcher requires the existence of a `.desktop` file to work, **Note:** Unity launcher requires the existence of a `.desktop` file to work,
for more information please read [Desktop Environment Integration][unity-requirement]. for more information please read [Desktop Environment Integration][unity-requirement].
**[Deprecated](modernization/property-updates.md)**
### `app.getBadgeCount()` _Linux_ _macOS_ ### `app.getBadgeCount()` _Linux_ _macOS_
Returns `Integer` - The current value displayed in the counter badge. Returns `Integer` - The current value displayed in the counter badge.
**[Deprecated](modernization/property-updates.md)**
### `app.isUnityRunning()` _Linux_ ### `app.isUnityRunning()` _Linux_
Returns `Boolean` - Whether the current desktop environment is Unity launcher. Returns `Boolean` - Whether the current desktop environment is Unity launcher.
@@ -1173,8 +1163,6 @@ technologies, such as screen readers, has been detected. See
https://www.chromium.org/developers/design-documents/accessibility for more https://www.chromium.org/developers/design-documents/accessibility for more
details. details.
**[Deprecated](modernization/property-updates.md)**
### `app.setAccessibilitySupportEnabled(enabled)` _macOS_ _Windows_ ### `app.setAccessibilitySupportEnabled(enabled)` _macOS_ _Windows_
* `enabled` Boolean - Enable or disable [accessibility tree](https://developers.google.com/web/fundamentals/accessibility/semantics-builtin/the-accessibility-tree) rendering * `enabled` Boolean - Enable or disable [accessibility tree](https://developers.google.com/web/fundamentals/accessibility/semantics-builtin/the-accessibility-tree) rendering
@@ -1186,8 +1174,6 @@ This API must be called after the `ready` event is emitted.
**Note:** Rendering accessibility tree can significantly affect the performance of your app. It should not be enabled by default. **Note:** Rendering accessibility tree can significantly affect the performance of your app. It should not be enabled by default.
**[Deprecated](modernization/property-updates.md)**
### `app.showAboutPanel()` ### `app.showAboutPanel()`
Show the app's about panel options. These options can be overridden with `app.setAboutPanelOptions(options)`. Show the app's about panel options. These options can be overridden with `app.setAboutPanelOptions(options)`.
@@ -1327,6 +1313,8 @@ A `Boolean` property that returns `true` if the app is packaged, `false` otherw
[dock-menu]:https://developer.apple.com/macos/human-interface-guidelines/menus/dock-menus/ [dock-menu]:https://developer.apple.com/macos/human-interface-guidelines/menus/dock-menus/
[tasks]:https://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks [tasks]:https://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks
[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx
[electron-forge]: https://www.electronforge.io/
[electron-packager]: https://github.com/electron/electron-packager
[CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115 [CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115
[LSCopyDefaultHandlerForURLScheme]: https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme [LSCopyDefaultHandlerForURLScheme]: https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme
[handoff]: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html [handoff]: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html

View File

@@ -289,7 +289,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
between the web pages even when you specified different values for them, between the web pages even when you specified different values for them,
including but not limited to `preload`, `sandbox` and `nodeIntegration`. including but not limited to `preload`, `sandbox` and `nodeIntegration`.
So it is suggested to use exact same `webPreferences` for web pages with So it is suggested to use exact same `webPreferences` for web pages with
the same `affinity`. _This property is experimental_ the same `affinity`. _Deprecated_
* `zoomFactor` Number (optional) - The default zoom factor of the page, `3.0` represents * `zoomFactor` Number (optional) - The default zoom factor of the page, `3.0` represents
`300%`. Default is `1.0`. `300%`. Default is `1.0`.
* `javascript` Boolean (optional) - Enables JavaScript support. Default is `true`. * `javascript` Boolean (optional) - Enables JavaScript support. Default is `true`.
@@ -369,6 +369,8 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
consecutive dialog protection is triggered. If not defined the default consecutive dialog protection is triggered. If not defined the default
message would be used, note that currently the default message is in message would be used, note that currently the default message is in
English and not localized. English and not localized.
* `disableDialogs` Boolean (optional) - Whether to disable dialogs
completely. Overrides `safeDialogs`. Default is `false`.
* `navigateOnDragDrop` Boolean (optional) - Whether dragging and dropping a * `navigateOnDragDrop` Boolean (optional) - Whether dragging and dropping a
file or link onto the page causes a navigation. Default is `false`. file or link onto the page causes a navigation. Default is `false`.
* `autoplayPolicy` String (optional) - Autoplay policy to apply to * `autoplayPolicy` String (optional) - Autoplay policy to apply to
@@ -621,6 +623,12 @@ Returns:
Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`. Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`.
The method underlying this event is built to handle older macOS-style trackpad swiping,
where the content on the screen doesn't move with the swipe. Most macOS trackpads are not
configured to allow this kind of swiping anymore, so in order for it to emit properly the
'Swipe between pages' preference in `System Preferences > Trackpad > More Gestures` must be
set to 'Swipe with two or three fingers'.
#### Event: 'rotate-gesture' _macOS_ #### Event: 'rotate-gesture' _macOS_
Returns: Returns:
@@ -1113,15 +1121,11 @@ Returns `Integer[]` - Contains the window's maximum width and height.
* `resizable` Boolean * `resizable` Boolean
Sets whether the window can be manually resized by user. Sets whether the window can be manually resized by the user.
**[Deprecated](modernization/property-updates.md)**
#### `win.isResizable()` #### `win.isResizable()`
Returns `Boolean` - Whether the window can be manually resized by user. Returns `Boolean` - Whether the window can be manually resized by the user.
**[Deprecated](modernization/property-updates.md)**
#### `win.setMovable(movable)` _macOS_ _Windows_ #### `win.setMovable(movable)` _macOS_ _Windows_
@@ -1129,41 +1133,29 @@ Returns `Boolean` - Whether the window can be manually resized by user.
Sets whether the window can be moved by user. On Linux does nothing. Sets whether the window can be moved by user. On Linux does nothing.
**[Deprecated](modernization/property-updates.md)**
#### `win.isMovable()` _macOS_ _Windows_ #### `win.isMovable()` _macOS_ _Windows_
Returns `Boolean` - Whether the window can be moved by user. Returns `Boolean` - Whether the window can be moved by user.
On Linux always returns `true`. On Linux always returns `true`.
**[Deprecated](modernization/property-updates.md)**
#### `win.setMinimizable(minimizable)` _macOS_ _Windows_ #### `win.setMinimizable(minimizable)` _macOS_ _Windows_
* `minimizable` Boolean * `minimizable` Boolean
Sets whether the window can be manually minimized by user. On Linux does Sets whether the window can be manually minimized by user. On Linux does nothing.
nothing.
**[Deprecated](modernization/property-updates.md)**
#### `win.isMinimizable()` _macOS_ _Windows_ #### `win.isMinimizable()` _macOS_ _Windows_
Returns `Boolean` - Whether the window can be manually minimized by user Returns `Boolean` - Whether the window can be manually minimized by the user.
On Linux always returns `true`. On Linux always returns `true`.
**[Deprecated](modernization/property-updates.md)**
#### `win.setMaximizable(maximizable)` _macOS_ _Windows_ #### `win.setMaximizable(maximizable)` _macOS_ _Windows_
* `maximizable` Boolean * `maximizable` Boolean
Sets whether the window can be manually maximized by user. On Linux does Sets whether the window can be manually maximized by user. On Linux does nothing.
nothing.
**[Deprecated](modernization/property-updates.md)**
#### `win.isMaximizable()` _macOS_ _Windows_ #### `win.isMaximizable()` _macOS_ _Windows_
@@ -1171,23 +1163,15 @@ Returns `Boolean` - Whether the window can be manually maximized by user.
On Linux always returns `true`. On Linux always returns `true`.
**[Deprecated](modernization/property-updates.md)**
#### `win.setFullScreenable(fullscreenable)` #### `win.setFullScreenable(fullscreenable)`
* `fullscreenable` Boolean * `fullscreenable` Boolean
Sets whether the maximize/zoom window button toggles fullscreen mode or Sets whether the maximize/zoom window button toggles fullscreen mode or maximizes the window.
maximizes the window.
**[Deprecated](modernization/property-updates.md)**
#### `win.isFullScreenable()` #### `win.isFullScreenable()`
Returns `Boolean` - Whether the maximize/zoom window button toggles fullscreen mode or Returns `Boolean` - Whether the maximize/zoom window button toggles fullscreen mode or maximizes the window.
maximizes the window.
**[Deprecated](modernization/property-updates.md)**
#### `win.setClosable(closable)` _macOS_ _Windows_ #### `win.setClosable(closable)` _macOS_ _Windows_
@@ -1195,16 +1179,12 @@ maximizes the window.
Sets whether the window can be manually closed by user. On Linux does nothing. Sets whether the window can be manually closed by user. On Linux does nothing.
**[Deprecated](modernization/property-updates.md)**
#### `win.isClosable()` _macOS_ _Windows_ #### `win.isClosable()` _macOS_ _Windows_
Returns `Boolean` - Whether the window can be manually closed by user. Returns `Boolean` - Whether the window can be manually closed by user.
On Linux always returns `true`. On Linux always returns `true`.
**[Deprecated](modernization/property-updates.md)**
#### `win.setAlwaysOnTop(flag[, level][, relativeLevel])` #### `win.setAlwaysOnTop(flag[, level][, relativeLevel])`
* `flag` Boolean * `flag` Boolean
@@ -1616,23 +1596,17 @@ This cannot be called when `titleBarStyle` is set to `customButtonsOnHover`.
Sets whether the window menu bar should hide itself automatically. Once set the Sets whether the window menu bar should hide itself automatically. Once set the
menu bar will only show when users press the single `Alt` key. menu bar will only show when users press the single `Alt` key.
If the menu bar is already visible, calling `setAutoHideMenuBar(true)` won't If the menu bar is already visible, calling `setAutoHideMenuBar(true)` won't hide it immediately.
hide it immediately.
**[Deprecated](modernization/property-updates.md)**
#### `win.isMenuBarAutoHide()` #### `win.isMenuBarAutoHide()`
Returns `Boolean` - Whether menu bar automatically hides itself. Returns `Boolean` - Whether menu bar automatically hides itself.
**[Deprecated](modernization/property-updates.md)**
#### `win.setMenuBarVisibility(visible)` _Windows_ _Linux_ #### `win.setMenuBarVisibility(visible)` _Windows_ _Linux_
* `visible` Boolean * `visible` Boolean
Sets whether the menu bar should be visible. If the menu bar is auto-hide, users Sets whether the menu bar should be visible. If the menu bar is auto-hide, users can still bring up the menu bar by pressing the single `Alt` key.
can still bring up the menu bar by pressing the single `Alt` key.
#### `win.isMenuBarVisible()` #### `win.isMenuBarVisible()`
@@ -1748,6 +1722,17 @@ will remove the vibrancy effect on the window.
Note that `appearance-based`, `light`, `dark`, `medium-light`, and `ultra-dark` have been Note that `appearance-based`, `light`, `dark`, `medium-light`, and `ultra-dark` have been
deprecated and will be removed in an upcoming version of macOS. deprecated and will be removed in an upcoming version of macOS.
#### `win.setTrafficLightPosition(position)` _macOS_
* `position` [Point](structures/point.md)
Set a custom position for the traffic light buttons. Can only be used with `titleBarStyle` set to `hidden`.
#### `win.getTrafficLightPosition()` _macOS_
Returns `Point` - The current position for the traffic light buttons. Can only be used with `titleBarStyle`
set to `hidden`.
#### `win.setTouchBar(touchBar)` _macOS_ _Experimental_ #### `win.setTouchBar(touchBar)` _macOS_ _Experimental_
* `touchBar` TouchBar | null * `touchBar` TouchBar | null

View File

@@ -22,6 +22,9 @@ which the request is associated.
with which the request is associated. Defaults to the empty string. The with which the request is associated. Defaults to the empty string. The
`session` option prevails on `partition`. Thus if a `session` is explicitly `session` option prevails on `partition`. Thus if a `session` is explicitly
specified, `partition` is ignored. specified, `partition` is ignored.
* `useSessionCookies` Boolean (optional) - Whether to send cookies with this
request from the provided session. This will make the `net` request's
cookie behavior match a `fetch` request. Default is `false`.
* `protocol` String (optional) - The protocol scheme in the form 'scheme:'. * `protocol` String (optional) - The protocol scheme in the form 'scheme:'.
Currently supported values are 'http:' or 'https:'. Defaults to 'http:'. Currently supported values are 'http:' or 'https:'. Defaults to 'http:'.
* `host` String (optional) - The server host provided as a concatenation of * `host` String (optional) - The server host provided as a concatenation of

View File

@@ -269,6 +269,7 @@ Shows a message box, it will block the process until the message box is closed.
It returns the index of the clicked button. It returns the index of the clicked button.
The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal. The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal.
If `browserWindow` is not shown dialog will not be attached to it. In such case It will be displayed as independed window.
### `dialog.showMessageBox([browserWindow, ]options)` ### `dialog.showMessageBox([browserWindow, ]options)`

View File

@@ -82,16 +82,12 @@ The API is only available in session's `will-download` callback function.
If user doesn't set the save path via the API, Electron will use the original If user doesn't set the save path via the API, Electron will use the original
routine to determine the save path; this usually prompts a save dialog. routine to determine the save path; this usually prompts a save dialog.
**[Deprecated](modernization/property-updates.md): use the `savePath` property instead.**
#### `downloadItem.getSavePath()` #### `downloadItem.getSavePath()`
Returns `String` - The save path of the download item. This will be either the path Returns `String` - The save path of the download item. This will be either the path
set via `downloadItem.setSavePath(path)` or the path selected from the shown set via `downloadItem.setSavePath(path)` or the path selected from the shown
save dialog. save dialog.
**[Deprecated](modernization/property-updates.md): use the `savePath` property instead.**
#### `downloadItem.setSaveDialogOptions(options)` #### `downloadItem.setSaveDialogOptions(options)`
* `options` SaveDialogOptions - Set the save file dialog options. This object has the same * `options` SaveDialogOptions - Set the save file dialog options. This object has the same

View File

@@ -53,16 +53,23 @@ Unsupported options are:
### `GOOGLE_API_KEY` ### `GOOGLE_API_KEY`
You can provide an API key for making requests to Google's geocoding webservice. To do this, place the following code in your main process Geolocation support in Electron requires the use of Google Cloud Platform's
file, before opening any browser windows that will make geocoding requests: geolocation webservice. To enable this feature, acquire a
[Google API key](https://developers.google.com/maps/documentation/geolocation/get-api-key)
and place the following code in your main process file, before opening any
browser windows that will make geolocation requests:
```javascript ```javascript
process.env.GOOGLE_API_KEY = 'YOUR_KEY_HERE' process.env.GOOGLE_API_KEY = 'YOUR_KEY_HERE'
``` ```
For instructions on how to acquire a Google API key, visit [this page](https://developers.google.com/maps/documentation/javascript/get-api-key). By default, a newly generated Google API key may not be allowed to make geolocation requests.
By default, a newly generated Google API key may not be allowed to make To enable the geolocation webservice for your project, enable it through the
geocoding requests. To enable geocoding requests, visit [this page](https://developers.google.com/maps/documentation/geocoding/get-api-key). [API library](https://console.cloud.google.com/apis/library).
N.B. You will need to add a
[Billing Account](https://cloud.google.com/billing/docs/how-to/payment-methods#add_a_payment_method)
to the project associated to the API key for the geolocation webservice to work.
### `ELECTRON_NO_ASAR` ### `ELECTRON_NO_ASAR`

View File

@@ -45,9 +45,3 @@ The Electron team is currently undergoing an initiative to convert separate gett
* `isMacTemplateImage` * `isMacTemplateImage`
* `SystemPreferences` module * `SystemPreferences` module
* `appLevelAppearance` * `appLevelAppearance`
* `webContents` module
* `audioMuted`
* `frameRate`
* `userAgent`
* `zoomFactor`
* `zoomLevel`

View File

@@ -276,14 +276,10 @@ Returns [`Size`](structures/size.md)
Marks the image as a template image. Marks the image as a template image.
**[Deprecated](modernization/property-updates.md)**
#### `image.isTemplateImage()` #### `image.isTemplateImage()`
Returns `Boolean` - Whether the image is a template image. Returns `Boolean` - Whether the image is a template image.
**[Deprecated](modernization/property-updates.md)**
#### `image.crop(rect)` #### `image.crop(rect)`
* `rect` [Rectangle](structures/rectangle.md) - The area of the image to crop. * `rect` [Rectangle](structures/rectangle.md) - The area of the image to crop.

View File

@@ -0,0 +1,62 @@
## Class: ServiceWorkers
> Query and receive events from a sessions active service workers.
Process: [Main](../glossary.md#main-process)
Instances of the `ServiceWorkers` class are accessed by using `serviceWorkers` property of
a `Session`.
For example:
```javascript
const { session } = require('electron')
// Get all service workers.
console.log(session.defaultSession.serviceWorkers.getAllRunning())
// Handle logs and get service worker info
session.defaultSession.serviceWorkers.on('console-message', (event, messageDetails) => {
console.log(
'Got service worker message',
messageDetails,
'from',
session.defaultSession.serviceWorkers.getFromVersionID(messageDetails.versionId)
)
})
```
### Instance Events
The following events are available on instances of `ServiceWorkers`:
#### Event: 'console-message'
Returns:
* `event` Event
* `messageDetails` Object - Information about the console message
* `message` String - The actual console message
* `versionId` Number - The version ID of the service worker that sent the log message
* `source` String - The type of source for this message. Can be `javascript`, `xml`, `network`, `console-api`, `storage`, `app-cache`, `rendering`, `security`, `deprecation`, `worker`, `violation`, `intervention`, `recommendation` or `other`.
* `level` Number - The log level, from 0 to 3. In order it matches `verbose`, `info`, `warning` and `error`.
* `sourceUrl` String - The URL the message came from
* `lineNumber` Number - The line number of the source that triggered this console message
Emitted when a service worker logs something to the console.
### Instance Methods
The following methods are available on instances of `ServiceWorkers`:
#### `serviceWorkers.getAllRunning()`
Returns `Record<Number, ServiceWorkerInfo>` - A [ServiceWorkerInfo](structures/service-worker-info.md) object where the keys are the service worker version ID and the values are the information about that service worker.
#### `serviceWorkers.getFromVersionID(versionId)`
* `versionId` Number
Returns [`ServiceWorkerInfo`](structures/service-worker-info.md) - Information about this service worker
If the service worker does not exist or is not running this method will throw an exception.

View File

@@ -105,6 +105,45 @@ Returns:
Emitted when a render process requests preconnection to a URL, generally due to Emitted when a render process requests preconnection to a URL, generally due to
a [resource hint](https://w3c.github.io/resource-hints/). a [resource hint](https://w3c.github.io/resource-hints/).
#### Event: 'spellcheck-dictionary-initialized'
Returns:
* `event` Event
* `languageCode` String - The language code of the dictionary file
Emitted when a hunspell dictionary file has been successfully initialized. This
occurs after the file has been downloaded.
#### Event: 'spellcheck-dictionary-download-begin'
Returns:
* `event` Event
* `languageCode` String - The language code of the dictionary file
Emitted when a hunspell dictionary file starts downloading
#### Event: 'spellcheck-dictionary-download-success'
Returns:
* `event` Event
* `languageCode` String - The language code of the dictionary file
Emitted when a hunspell dictionary file has been successfully downloaded
#### Event: 'spellcheck-dictionary-download-failure'
Returns:
* `event` Event
* `languageCode` String - The language code of the dictionary file
Emitted when a hunspell dictionary file download fails. For details
on the failure you should collect a netlog and inspect the download
request.
### Instance Methods ### Instance Methods
The following methods are available on instances of `Session`: The following methods are available on instances of `Session`:
@@ -483,18 +522,38 @@ setting with the current OS locale. This setting is persisted across restarts.
By default Electron will download hunspell dictionaries from the Chromium CDN. If you want to override this By default Electron will download hunspell dictionaries from the Chromium CDN. If you want to override this
behavior you can use this API to point the dictionary downloader at your own hosted version of the hunspell behavior you can use this API to point the dictionary downloader at your own hosted version of the hunspell
dictionaries. We publish a `hunspell_dictionaries.zip` file with each release which contains the files you need dictionaries. We publish a `hunspell_dictionaries.zip` file with each release which contains the files you need
to host here. to host here, the file server must be **case insensitive** you must upload each file twice, once with the case it
has in the ZIP file and once with the filename as all lower case.
If the files present in `hunspell_dictionaries.zip` are available at `https://example.com/dictionaries/language-code.bdic`
then you should call this api with `ses.setSpellCheckerDictionaryDownloadURL('https://example.com/dictionaries/')`. Please
note the trailing slash. The URL to the dictionaries is formed as `${url}${filename}`.
**Note:** On macOS the OS spellchecker is used and therefore we do not download any dictionary files. This API is a no-op on macOS. **Note:** On macOS the OS spellchecker is used and therefore we do not download any dictionary files. This API is a no-op on macOS.
#### `ses.listWordsInSpellCheckerDictionary()`
Returns `Promise<String[]>` - An array of all words in app's custom dictionary.
Resolves when the full dictionary is loaded from disk.
#### `ses.addWordToSpellCheckerDictionary(word)` #### `ses.addWordToSpellCheckerDictionary(word)`
* `word` String - The word you want to add to the dictionary * `word` String - The word you want to add to the dictionary
Returns `Boolean` - Whether the word was successfully written to the custom dictionary. Returns `Boolean` - Whether the word was successfully written to the custom dictionary. This API
will not work on non-persistent (in-memory) sessions.
**Note:** On macOS and Windows 10 this word will be written to the OS custom dictionary as well **Note:** On macOS and Windows 10 this word will be written to the OS custom dictionary as well
#### `ses.removeWordFromSpellCheckerDictionary(word)`
* `word` String - The word you want to remove from the dictionary
Returns `Boolean` - Whether the word was successfully removed from the custom dictionary. This API
will not work on non-persistent (in-memory) sessions.
**Note:** On macOS and Windows 10 this word will be removed from the OS custom dictionary as well
#### `ses.loadExtension(path)` #### `ses.loadExtension(path)`
* `path` String - Path to a directory containing an unpacked Chrome extension * `path` String - Path to a directory containing an unpacked Chrome extension
@@ -567,6 +626,10 @@ code to the `setSpellCheckerLanaguages` API that isn't in this array will result
A [`Cookies`](cookies.md) object for this session. A [`Cookies`](cookies.md) object for this session.
#### `ses.serviceWorkers` _Readonly_
A [`ServiceWorkers`](service-workers.md) object for this session.
#### `ses.webRequest` _Readonly_ #### `ses.webRequest` _Readonly_
A [`WebRequest`](web-request.md) object for this session. A [`WebRequest`](web-request.md) object for this session.

View File

@@ -1,5 +1,8 @@
# Extension Object # Extension Object
* `id` String * `id` String
* `manifest` any - Copy of the [extension's manifest data](https://developer.chrome.com/extensions/manifest).
* `name` String * `name` String
* `path` String - The extension's file path.
* `version` String * `version` String
* `url` String - The extension's `chrome-extension://` URL.

View File

@@ -0,0 +1,5 @@
# ServiceWorkerInfo Object
* `scriptUrl` String - The full URL to the script that this service worker runs
* `scope` String - The base URL that this service worker is active for.
* `renderProcessId` Number - The virtual ID of the process that this service worker is running in. This is not an OS level PID. This aligns with the ID set used for `webContents.getProcessId()`.

View File

@@ -360,7 +360,7 @@ Returns `Boolean` - `true` if an inverted color scheme (a high contrast color sc
Returns `Boolean` - `true` if a high contrast theme is active, `false` otherwise. Returns `Boolean` - `true` if a high contrast theme is active, `false` otherwise.
**Depreacted:** Should use the new [`nativeTheme.shouldUseHighContrastColors`](native-theme.md#nativethemeshouldusehighcontrastcolors-macos-windows-readonly) API. **Deprecated:** Should use the new [`nativeTheme.shouldUseHighContrastColors`](native-theme.md#nativethemeshouldusehighcontrastcolors-macos-windows-readonly) API.
### `systemPreferences.getEffectiveAppearance()` _macOS_ ### `systemPreferences.getEffectiveAppearance()` _macOS_
@@ -369,16 +369,6 @@ Returns `String` - Can be `dark`, `light` or `unknown`.
Gets the macOS appearance setting that is currently applied to your application, Gets the macOS appearance setting that is currently applied to your application,
maps to [NSApplication.effectiveAppearance](https://developer.apple.com/documentation/appkit/nsapplication/2967171-effectiveappearance?language=objc) maps to [NSApplication.effectiveAppearance](https://developer.apple.com/documentation/appkit/nsapplication/2967171-effectiveappearance?language=objc)
Please note that until Electron is built targeting the 10.14 SDK, your application's
`effectiveAppearance` will default to 'light' and won't inherit the OS preference. In
the interim in order for your application to inherit the OS preference you must set the
`NSRequiresAquaSystemAppearance` key in your apps `Info.plist` to `false`. If you are
using `electron-packager` or `electron-forge` just set the `enableDarwinDarkMode`
packager option to `true`. See the [Electron Packager API](https://github.com/electron/electron-packager/blob/master/docs/api.md#darwindarkmodesupport)
for more details.
**[Deprecated](modernization/property-updates.md)**
### `systemPreferences.getAppLevelAppearance()` _macOS_ _Deprecated_ ### `systemPreferences.getAppLevelAppearance()` _macOS_ _Deprecated_
Returns `String` | `null` - Can be `dark`, `light` or `unknown`. Returns `String` | `null` - Can be `dark`, `light` or `unknown`.
@@ -387,8 +377,6 @@ Gets the macOS appearance setting that you have declared you want for
your application, maps to [NSApplication.appearance](https://developer.apple.com/documentation/appkit/nsapplication/2967170-appearance?language=objc). your application, maps to [NSApplication.appearance](https://developer.apple.com/documentation/appkit/nsapplication/2967170-appearance?language=objc).
You can use the `setAppLevelAppearance` API to set this value. You can use the `setAppLevelAppearance` API to set this value.
**[Deprecated](modernization/property-updates.md)**
### `systemPreferences.setAppLevelAppearance(appearance)` _macOS_ _Deprecated_ ### `systemPreferences.setAppLevelAppearance(appearance)` _macOS_ _Deprecated_
* `appearance` String | null - Can be `dark` or `light` * `appearance` String | null - Can be `dark` or `light`
@@ -396,16 +384,12 @@ You can use the `setAppLevelAppearance` API to set this value.
Sets the appearance setting for your application, this should override the Sets the appearance setting for your application, this should override the
system default and override the value of `getEffectiveAppearance`. system default and override the value of `getEffectiveAppearance`.
**[Deprecated](modernization/property-updates.md)**
### `systemPreferences.canPromptTouchID()` _macOS_ ### `systemPreferences.canPromptTouchID()` _macOS_
Returns `Boolean` - whether or not this device has the ability to use Touch ID. Returns `Boolean` - whether or not this device has the ability to use Touch ID.
**NOTE:** This API will return `false` on macOS systems older than Sierra 10.12.2. **NOTE:** This API will return `false` on macOS systems older than Sierra 10.12.2.
**[Deprecated](modernization/property-updates.md)**
### `systemPreferences.promptTouchID(reason)` _macOS_ ### `systemPreferences.promptTouchID(reason)` _macOS_
* `reason` String - The reason you are asking for Touch ID authentication * `reason` String - The reason you are asking for Touch ID authentication
@@ -480,11 +464,3 @@ A `String` property that can be `dark`, `light` or `unknown`.
Returns the macOS appearance setting that is currently applied to your application, Returns the macOS appearance setting that is currently applied to your application,
maps to [NSApplication.effectiveAppearance](https://developer.apple.com/documentation/appkit/nsapplication/2967171-effectiveappearance?language=objc) maps to [NSApplication.effectiveAppearance](https://developer.apple.com/documentation/appkit/nsapplication/2967171-effectiveappearance?language=objc)
Please note that until Electron is built targeting the 10.14 SDK, your application's
`effectiveAppearance` will default to 'light' and won't inherit the OS preference. In
the interim in order for your application to inherit the OS preference you must set the
`NSRequiresAquaSystemAppearance` key in your apps `Info.plist` to `false`. If you are
using `electron-packager` or `electron-forge` just set the `enableDarwinDarkMode`
packager option to `true`. See the [Electron Packager API](https://github.com/electron/electron-packager/blob/master/docs/api.md#darwindarkmodesupport)
for more details.

View File

@@ -967,14 +967,10 @@ Returns `Boolean` - Whether the renderer process has crashed.
Overrides the user agent for this web page. Overrides the user agent for this web page.
**[Deprecated](modernization/property-updates.md)**
#### `contents.getUserAgent()` #### `contents.getUserAgent()`
Returns `String` - The user agent for this web page. Returns `String` - The user agent for this web page.
**[Deprecated](modernization/property-updates.md)**
#### `contents.insertCSS(css[, options])` #### `contents.insertCSS(css[, options])`
* `css` String * `css` String
@@ -1054,33 +1050,27 @@ Ignore application menu shortcuts while this web contents is focused.
Mute the audio on the current web page. Mute the audio on the current web page.
**[Deprecated](modernization/property-updates.md)**
#### `contents.isAudioMuted()` #### `contents.isAudioMuted()`
Returns `Boolean` - Whether this page has been muted. Returns `Boolean` - Whether this page has been muted.
**[Deprecated](modernization/property-updates.md)**
#### `contents.isCurrentlyAudible()` #### `contents.isCurrentlyAudible()`
Returns `Boolean` - Whether audio is currently playing. Returns `Boolean` - Whether audio is currently playing.
#### `contents.setZoomFactor(factor)` #### `contents.setZoomFactor(factor)`
* `factor` Number - Zoom factor. * `factor` Double - Zoom factor; default is 1.0.
Changes the zoom factor to the specified factor. Zoom factor is Changes the zoom factor to the specified factor. Zoom factor is
zoom percent divided by 100, so 300% = 3.0. zoom percent divided by 100, so 300% = 3.0.
**[Deprecated](modernization/property-updates.md)** The factor must be greater than 0.0.
#### `contents.getZoomFactor()` #### `contents.getZoomFactor()`
Returns `Number` - the current zoom factor. Returns `Number` - the current zoom factor.
**[Deprecated](modernization/property-updates.md)**
#### `contents.setZoomLevel(level)` #### `contents.setZoomLevel(level)`
* `level` Number - Zoom level. * `level` Number - Zoom level.
@@ -1090,14 +1080,10 @@ increment above or below represents zooming 20% larger or smaller to default
limits of 300% and 50% of original size, respectively. The formula for this is limits of 300% and 50% of original size, respectively. The formula for this is
`scale := 1.2 ^ level`. `scale := 1.2 ^ level`.
**[Deprecated](modernization/property-updates.md)**
#### `contents.getZoomLevel()` #### `contents.getZoomLevel()`
Returns `Number` - the current zoom level. Returns `Number` - the current zoom level.
**[Deprecated](modernization/property-updates.md)**
#### `contents.setVisualZoomLevelLimits(minimumLevel, maximumLevel)` #### `contents.setVisualZoomLevelLimits(minimumLevel, maximumLevel)`
* `minimumLevel` Number * `minimumLevel` Number
@@ -1286,7 +1272,7 @@ Returns [`PrinterInfo[]`](structures/printer-info.md)
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height`. `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height`.
* `callback` Function (optional) * `callback` Function (optional)
* `success` Boolean - Indicates success of the print call. * `success` Boolean - Indicates success of the print call.
* `failureReason` String - Called back if the print fails; can be `cancelled` or `failed`. * `failureReason` String - Error description called back if the print fails.
Prints window's web page. When `silent` is set to `true`, Electron will pick Prints window's web page. When `silent` is set to `true`, Electron will pick
the system's default printer if `deviceName` is empty and the default settings for printing. the system's default printer if `deviceName` is empty and the default settings for printing.
@@ -1709,14 +1695,10 @@ Returns `Boolean` - If *offscreen rendering* is enabled returns whether it is cu
If *offscreen rendering* is enabled sets the frame rate to the specified number. If *offscreen rendering* is enabled sets the frame rate to the specified number.
Only values between 1 and 60 are accepted. Only values between 1 and 60 are accepted.
**[Deprecated](modernization/property-updates.md)**
#### `contents.getFrameRate()` #### `contents.getFrameRate()`
Returns `Integer` - If *offscreen rendering* is enabled returns the current frame rate. Returns `Integer` - If *offscreen rendering* is enabled returns the current frame rate.
**[Deprecated](modernization/property-updates.md)**
#### `contents.invalidate()` #### `contents.invalidate()`
Schedules a full repaint of the window this web contents is in. Schedules a full repaint of the window this web contents is in.
@@ -1822,7 +1804,7 @@ A [`WebContents`](web-contents.md) instance that might own this `WebContents`.
#### `contents.devToolsWebContents` _Readonly_ #### `contents.devToolsWebContents` _Readonly_
A `WebContents` of DevTools for this `WebContents`. A `WebContents | null` property that represents the of DevTools `WebContents` associated with a given `WebContents`.
**Note:** Users should never store this object because it may become `null` **Note:** Users should never store this object because it may become `null`
when the DevTools has been closed. when the DevTools has been closed.

View File

@@ -22,11 +22,13 @@ The `WebFrame` class has the following instance methods:
### `webFrame.setZoomFactor(factor)` ### `webFrame.setZoomFactor(factor)`
* `factor` Number - Zoom factor. * `factor` Double - Zoom factor; default is 1.0.
Changes the zoom factor to the specified factor. Zoom factor is Changes the zoom factor to the specified factor. Zoom factor is
zoom percent divided by 100, so 300% = 3.0. zoom percent divided by 100, so 300% = 3.0.
The factor must be greater than 0.0.
### `webFrame.getZoomFactor()` ### `webFrame.getZoomFactor()`
Returns `Number` - The current zoom factor. Returns `Number` - The current zoom factor.
@@ -122,13 +124,20 @@ by its key, which is returned from `webFrame.insertCSS(css)`.
Inserts `text` to the focused element. Inserts `text` to the focused element.
### `webFrame.executeJavaScript(code[, userGesture])` ### `webFrame.executeJavaScript(code[, userGesture, callback])`
* `code` String * `code` String
* `userGesture` Boolean (optional) - Default is `false`. * `userGesture` Boolean (optional) - Default is `false`.
* `callback` Function (optional) - Called after script has been executed. Unless
the frame is suspended (e.g. showing a modal alert), execution will be
synchronous and the callback will be invoked before the method returns. For
compatibility with an older version of this method, the error parameter is
second.
* `result` Any
* `error` Error
Returns `Promise<any>` - A promise that resolves with the result of the executed code Returns `Promise<any>` - A promise that resolves with the result of the executed
or is rejected if the result of the code is a rejected promise. code or is rejected if execution throws or results in a rejected promise.
Evaluates `code` in page. Evaluates `code` in page.
@@ -136,14 +145,24 @@ In the browser window some HTML APIs like `requestFullScreen` can only be
invoked by a gesture from the user. Setting `userGesture` to `true` will remove invoked by a gesture from the user. Setting `userGesture` to `true` will remove
this limitation. this limitation.
### `webFrame.executeJavaScriptInIsolatedWorld(worldId, scripts[, userGesture])` ### `webFrame.executeJavaScriptInIsolatedWorld(worldId, scripts[, userGesture, callback])`
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. You can provide any integer here. * `worldId` Integer - The ID of the world to run the javascript
in, `0` is the default main world (where content runs), `999` is the
world used by Electron's `contextIsolation` feature. Accepts values
in the range 1..536870911.
* `scripts` [WebSource[]](structures/web-source.md) * `scripts` [WebSource[]](structures/web-source.md)
* `userGesture` Boolean (optional) - Default is `false`. * `userGesture` Boolean (optional) - Default is `false`.
* `callback` Function (optional) - Called after script has been executed. Unless
the frame is suspended (e.g. showing a modal alert), execution will be
synchronous and the callback will be invoked before the method returns. For
compatibility with an older version of this method, the error parameter is
second.
* `result` Any
* `error` Error
Returns `Promise<any>` - A promise that resolves with the result of the executed code Returns `Promise<any>` - A promise that resolves with the result of the executed
or is rejected if the result of the code is a rejected promise. code or is rejected if execution throws or results in a rejected promise.
Works like `executeJavaScript` but evaluates `scripts` in an isolated context. Works like `executeJavaScript` but evaluates `scripts` in an isolated context.

View File

@@ -146,6 +146,7 @@ response are visible by the time this listener is fired.
* `timestamp` Double * `timestamp` Double
* `statusLine` String * `statusLine` String
* `statusCode` Integer * `statusCode` Integer
* `requestHeaders` Record<string, string>
* `responseHeaders` Record<string, string[]> (optional) * `responseHeaders` Record<string, string[]> (optional)
* `callback` Function * `callback` Function
* `headersReceivedResponse` Object * `headersReceivedResponse` Object
@@ -228,6 +229,7 @@ redirect is about to occur.
* `fromCache` Boolean * `fromCache` Boolean
* `statusCode` Integer * `statusCode` Integer
* `statusLine` String * `statusLine` String
* `error` String
The `listener` will be called with `listener(details)` when a request is The `listener` will be called with `listener(details)` when a request is
completed. completed.

View File

@@ -1,29 +1,40 @@
# Mojave Dark Mode # Supporting macOS Dark Mode
In macOS 10.14 Mojave, Apple introduced a new [system-wide dark mode](https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/) In macOS 10.14 Mojave, Apple introduced a new [system-wide dark mode](https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/)
for all macOS computers. If your app does have a dark mode, you can make your Electron app for all macOS computers. If your Electron app has a dark mode, you can make it follow the
follow the system-wide dark mode setting using [the nativeTheme api](../api/native-theme.md). system-wide dark mode setting using [the `nativeTheme` api](../api/native-theme.md).
In macOS 10.15 Catalina, Apple introduced a new "automatic" dark mode option for all macOS computers. In order In macOS 10.15 Catalina, Apple introduced a new "automatic" dark mode option for all macOS computers.
for the `nativeTheme.shouldUseDarkColors` and `Tray` APIs to work correctly in this mode on Catalina you need to either have `NSRequiresAquaSystemAppearance` set to `false` in your `Info.plist` file or be on Electron `>=7.0.0`. In order for the `nativeTheme.shouldUseDarkColors` and `Tray` APIs to work correctly in this mode on
Catalina, you need to either have `NSRequiresAquaSystemAppearance` set to `false` in your
`Info.plist` file, or be on Electron `>=7.0.0`. Both [Electron Packager][electron-packager] and
[Electron Forge][electron-forge] have a [`darwinDarkModeSupport` option][packager-darwindarkmode-api]
to automate the `Info.plist` changes during app build time.
## Automatically updating the native interfaces ## Automatically updating the native interfaces
"Native Interfaces" include the file picker, window border, dialogs, context menus and more; basically anything where "Native Interfaces" include the file picker, window border, dialogs, context menus and more; basically,
the UI comes from macOS and not your app. The default behavior as of Electron 7.0.0 is to opt in to this automatic anything where the UI comes from macOS and not your app. As of Electron 7.0.0, the default behavior
theming from the OS. If you wish to opt out you must set the `NSRequiresAquaSystemAppearance` key in the `Info.plist` file is to opt in to this automatic theming from the OS. If you wish to opt out and are using Electron
to `true`. Please note that once Electron starts building against the 10.14 SDK it will not be possible for you to opt &gt; 8.0.0, you must set the `NSRequiresAquaSystemAppearance` key in the `Info.plist` file to `true`.
out of this theming. Please note that Electron 8.0.0 and above will not let your opt out of this theming, due to the use
of the macOS 10.14 SDK.
## Automatically updating your own interfaces ## Automatically updating your own interfaces
If your app has its own dark mode you should toggle it on and off in sync with the system's dark mode setting. You can do If your app has its own dark mode, you should toggle it on and off in sync with the system's dark
this by listening for the theme updated event on Electron's `nativeTheme` module. E.g. mode setting. You can do this by listening for the theme updated event on Electron's `nativeTheme` module.
```js For example:
```javascript
const { nativeTheme } = require('electron') const { nativeTheme } = require('electron')
nativeTheme.on('updated', function theThemeHasChanged () { nativeTheme.on('updated', function theThemeHasChanged () {
updateMyAppTheme(nativeTheme.shouldUseDarkColors) updateMyAppTheme(nativeTheme.shouldUseDarkColors)
}) })
``` ```
[electron-forge]: https://www.electronforge.io/
[electron-packager]: https://github.com/electron/electron-packager
[packager-darwindarkmode-api]: https://electron.github.io/electron-packager/master/interfaces/electronpackager.options.html#darwindarkmodesupport

View File

@@ -92,10 +92,14 @@ template("electron_extra_paks") {
} }
if (enable_electron_extensions) { if (enable_electron_extensions) {
sources += [ sources += [
"$root_gen_dir/chrome/component_extension_resources.pak",
"$root_gen_dir/extensions/extensions_renderer_resources.pak", "$root_gen_dir/extensions/extensions_renderer_resources.pak",
"$root_gen_dir/extensions/extensions_resources.pak", "$root_gen_dir/extensions/extensions_resources.pak",
] ]
deps += [ "//extensions:extensions_resources" ] deps += [
"//chrome/browser/resources:component_extension_resources",
"//extensions:extensions_resources",
]
} }
} }
} }

View File

@@ -17,7 +17,8 @@
<part file="electron_strings.grdp" /> <part file="electron_strings.grdp" />
</messages> </messages>
<includes> <includes>
<include name="IDR_CONTENT_SHELL_DEVTOOLS_DISCOVERY_PAGE" file="${target_gen_dir}\shell_devtools_discovery_page.html" use_base_dir="false" type="BINDATA" /> <include name="IDR_CONTENT_SHELL_DEVTOOLS_DISCOVERY_PAGE" file="${target_gen_dir}/shell_devtools_discovery_page.html" use_base_dir="false" type="BINDATA" />
<include name="IDR_PDF_MANIFEST" file="../chrome/browser/resources/pdf/manifest.json" type="BINDATA" />
</includes> </includes>
</release> </release>
</grit> </grit>

View File

@@ -45,6 +45,7 @@ auto_filenames = {
"docs/api/remote.md", "docs/api/remote.md",
"docs/api/sandbox-option.md", "docs/api/sandbox-option.md",
"docs/api/screen.md", "docs/api/screen.md",
"docs/api/service-workers.md",
"docs/api/session.md", "docs/api/session.md",
"docs/api/shell.md", "docs/api/shell.md",
"docs/api/structures", "docs/api/structures",
@@ -110,6 +111,7 @@ auto_filenames = {
"docs/api/structures/remove-password.md", "docs/api/structures/remove-password.md",
"docs/api/structures/scrubber-item.md", "docs/api/structures/scrubber-item.md",
"docs/api/structures/segmented-control-segment.md", "docs/api/structures/segmented-control-segment.md",
"docs/api/structures/service-worker-info.md",
"docs/api/structures/shared-worker-info.md", "docs/api/structures/shared-worker-info.md",
"docs/api/structures/shortcut-details.md", "docs/api/structures/shortcut-details.md",
"docs/api/structures/size.md", "docs/api/structures/size.md",

View File

@@ -88,6 +88,8 @@ filenames = {
"shell/browser/api/electron_api_protocol.h", "shell/browser/api/electron_api_protocol.h",
"shell/browser/api/electron_api_screen.cc", "shell/browser/api/electron_api_screen.cc",
"shell/browser/api/electron_api_screen.h", "shell/browser/api/electron_api_screen.h",
"shell/browser/api/electron_api_service_worker_context.cc",
"shell/browser/api/electron_api_service_worker_context.h",
"shell/browser/api/electron_api_session.cc", "shell/browser/api/electron_api_session.cc",
"shell/browser/api/electron_api_session.h", "shell/browser/api/electron_api_session.h",
"shell/browser/api/electron_api_system_preferences.cc", "shell/browser/api/electron_api_system_preferences.cc",
@@ -234,12 +236,15 @@ filenames = {
"shell/browser/net/node_stream_loader.h", "shell/browser/net/node_stream_loader.h",
"shell/browser/net/proxying_url_loader_factory.cc", "shell/browser/net/proxying_url_loader_factory.cc",
"shell/browser/net/proxying_url_loader_factory.h", "shell/browser/net/proxying_url_loader_factory.h",
"shell/browser/net/proxying_websocket.cc",
"shell/browser/net/proxying_websocket.h",
"shell/browser/net/resolve_proxy_helper.cc", "shell/browser/net/resolve_proxy_helper.cc",
"shell/browser/net/resolve_proxy_helper.h", "shell/browser/net/resolve_proxy_helper.h",
"shell/browser/net/system_network_context_manager.cc", "shell/browser/net/system_network_context_manager.cc",
"shell/browser/net/system_network_context_manager.h", "shell/browser/net/system_network_context_manager.h",
"shell/browser/net/url_pipe_loader.cc", "shell/browser/net/url_pipe_loader.cc",
"shell/browser/net/url_pipe_loader.h", "shell/browser/net/url_pipe_loader.h",
"shell/browser/net/web_request_api_interface.h",
"shell/browser/network_hints_handler_impl.cc", "shell/browser/network_hints_handler_impl.cc",
"shell/browser/network_hints_handler_impl.h", "shell/browser/network_hints_handler_impl.h",
"shell/browser/node_debugger.cc", "shell/browser/node_debugger.cc",
@@ -276,6 +281,8 @@ filenames = {
"shell/browser/notifications/win/win32_notification.h", "shell/browser/notifications/win/win32_notification.h",
"shell/browser/notifications/win/windows_toast_notification.cc", "shell/browser/notifications/win/windows_toast_notification.cc",
"shell/browser/notifications/win/windows_toast_notification.h", "shell/browser/notifications/win/windows_toast_notification.h",
"shell/browser/plugins/plugin_utils.cc",
"shell/browser/plugins/plugin_utils.h",
"shell/browser/pref_store_delegate.cc", "shell/browser/pref_store_delegate.cc",
"shell/browser/pref_store_delegate.h", "shell/browser/pref_store_delegate.h",
"shell/browser/relauncher.cc", "shell/browser/relauncher.cc",
@@ -551,8 +558,11 @@ filenames = {
"shell/common/skia_util.h", "shell/common/skia_util.h",
"shell/common/v8_value_converter.cc", "shell/common/v8_value_converter.cc",
"shell/common/v8_value_converter.h", "shell/common/v8_value_converter.h",
"shell/renderer/api/context_bridge/render_frame_context_bridge_store.cc", "shell/common/world_ids.h",
"shell/renderer/api/context_bridge/render_frame_context_bridge_store.h", "shell/renderer/api/context_bridge/object_cache.cc",
"shell/renderer/api/context_bridge/object_cache.h",
"shell/renderer/api/context_bridge/render_frame_function_store.cc",
"shell/renderer/api/context_bridge/render_frame_function_store.h",
"shell/renderer/api/electron_api_context_bridge.cc", "shell/renderer/api/electron_api_context_bridge.cc",
"shell/renderer/api/electron_api_context_bridge.h", "shell/renderer/api/electron_api_context_bridge.h",
"shell/renderer/api/electron_api_renderer_ipc.cc", "shell/renderer/api/electron_api_renderer_ipc.cc",
@@ -591,44 +601,54 @@ filenames = {
] ]
lib_sources_extensions = [ lib_sources_extensions = [
"shell/browser/extensions/api/i18n/i18n_api.cc",
"shell/browser/extensions/api/i18n/i18n_api.h",
"shell/browser/extensions/api/resources_private/resources_private_api.cc",
"shell/browser/extensions/api/resources_private/resources_private_api.h",
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc", "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc",
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h", "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h",
"shell/browser/extensions/api/tabs/tabs_api.cc", "shell/browser/extensions/api/tabs/tabs_api.cc",
"shell/browser/extensions/api/tabs/tabs_api.h", "shell/browser/extensions/api/tabs/tabs_api.h",
"shell/browser/extensions/electron_extensions_browser_client.cc", "shell/browser/extensions/api/streams_private/streams_private_api.cc",
"shell/browser/extensions/electron_extensions_browser_client.h", "shell/browser/extensions/api/streams_private/streams_private_api.h",
"shell/browser/extensions/electron_browser_context_keyed_service_factories.cc", "shell/browser/extensions/electron_browser_context_keyed_service_factories.cc",
"shell/browser/extensions/electron_browser_context_keyed_service_factories.h", "shell/browser/extensions/electron_browser_context_keyed_service_factories.h",
"shell/browser/extensions/electron_component_extension_resource_manager.cc",
"shell/browser/extensions/electron_component_extension_resource_manager.h",
"shell/browser/extensions/electron_display_info_provider.cc", "shell/browser/extensions/electron_display_info_provider.cc",
"shell/browser/extensions/electron_display_info_provider.h", "shell/browser/extensions/electron_display_info_provider.h",
"shell/browser/extensions/electron_extension_host_delegate.cc", "shell/browser/extensions/electron_extension_host_delegate.cc",
"shell/browser/extensions/electron_extension_host_delegate.h", "shell/browser/extensions/electron_extension_host_delegate.h",
"shell/browser/extensions/electron_extension_loader.cc", "shell/browser/extensions/electron_extension_loader.cc",
"shell/browser/extensions/electron_extension_loader.h", "shell/browser/extensions/electron_extension_loader.h",
"shell/browser/extensions/electron_extension_message_filter.cc",
"shell/browser/extensions/electron_extension_message_filter.h",
"shell/browser/extensions/electron_extension_system.cc", "shell/browser/extensions/electron_extension_system.cc",
"shell/browser/extensions/electron_extension_system.h", "shell/browser/extensions/electron_extension_system.h",
"shell/browser/extensions/electron_extension_system_factory.cc", "shell/browser/extensions/electron_extension_system_factory.cc",
"shell/browser/extensions/electron_extension_system_factory.h", "shell/browser/extensions/electron_extension_system_factory.h",
"shell/browser/extensions/electron_extension_web_contents_observer.cc", "shell/browser/extensions/electron_extension_web_contents_observer.cc",
"shell/browser/extensions/electron_extension_web_contents_observer.h", "shell/browser/extensions/electron_extension_web_contents_observer.h",
"shell/browser/extensions/electron_navigation_ui_data.cc",
"shell/browser/extensions/electron_navigation_ui_data.h",
"shell/browser/extensions/electron_process_manager_delegate.cc",
"shell/browser/extensions/electron_process_manager_delegate.h",
"shell/browser/extensions/electron_extensions_api_client.cc", "shell/browser/extensions/electron_extensions_api_client.cc",
"shell/browser/extensions/electron_extensions_api_client.h", "shell/browser/extensions/electron_extensions_api_client.h",
"shell/browser/extensions/electron_extensions_browser_api_provider.cc", "shell/browser/extensions/electron_extensions_browser_api_provider.cc",
"shell/browser/extensions/electron_extensions_browser_api_provider.h", "shell/browser/extensions/electron_extensions_browser_api_provider.h",
"shell/browser/extensions/electron_extensions_browser_client.cc",
"shell/browser/extensions/electron_extensions_browser_client.h",
"shell/browser/extensions/electron_messaging_delegate.cc", "shell/browser/extensions/electron_messaging_delegate.cc",
"shell/browser/extensions/electron_messaging_delegate.h", "shell/browser/extensions/electron_messaging_delegate.h",
"shell/browser/extensions/electron_navigation_ui_data.cc",
"shell/browser/extensions/electron_navigation_ui_data.h",
"shell/browser/extensions/electron_process_manager_delegate.cc",
"shell/browser/extensions/electron_process_manager_delegate.h",
"shell/common/extensions/electron_extensions_api_provider.cc", "shell/common/extensions/electron_extensions_api_provider.cc",
"shell/common/extensions/electron_extensions_api_provider.h", "shell/common/extensions/electron_extensions_api_provider.h",
"shell/common/extensions/electron_extensions_client.cc", "shell/common/extensions/electron_extensions_client.cc",
"shell/common/extensions/electron_extensions_client.h", "shell/common/extensions/electron_extensions_client.h",
"shell/renderer/extensions/electron_extensions_renderer_client.cc",
"shell/renderer/extensions/electron_extensions_renderer_client.h",
"shell/renderer/extensions/electron_extensions_dispatcher_delegate.cc", "shell/renderer/extensions/electron_extensions_dispatcher_delegate.cc",
"shell/renderer/extensions/electron_extensions_dispatcher_delegate.h", "shell/renderer/extensions/electron_extensions_dispatcher_delegate.h",
"shell/renderer/extensions/electron_extensions_renderer_client.cc",
"shell/renderer/extensions/electron_extensions_renderer_client.h",
] ]
app_sources = [ app_sources = [

View File

@@ -44,7 +44,7 @@ hunspell_dictionaries = [
"//third_party/hunspell_dictionaries/ta-IN-3-0.bdic", "//third_party/hunspell_dictionaries/ta-IN-3-0.bdic",
"//third_party/hunspell_dictionaries/tg-TG-5-0.bdic", "//third_party/hunspell_dictionaries/tg-TG-5-0.bdic",
"//third_party/hunspell_dictionaries/tr-TR-4-0.bdic", "//third_party/hunspell_dictionaries/tr-TR-4-0.bdic",
"//third_party/hunspell_dictionaries/uk-UA-3-0.bdic", "//third_party/hunspell_dictionaries/uk-UA-4-0.bdic",
"//third_party/hunspell_dictionaries/vi-VN-3-0.bdic", "//third_party/hunspell_dictionaries/vi-VN-3-0.bdic",
"//third_party/hunspell_dictionaries/xx-XX-3-0.bdic", "//third_party/hunspell_dictionaries/xx-XX-3-0.bdic",
] ]

View File

@@ -1,21 +1,44 @@
import * as fs from 'fs' import * as fs from 'fs';
import * as path from 'path' import * as path from 'path';
import { deprecate, Menu } from 'electron' import { Menu } from 'electron';
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
const bindings = process.electronBinding('app') const bindings = process.electronBinding('app');
const commandLine = process.electronBinding('command_line') const commandLine = process.electronBinding('command_line');
const { app, App } = bindings const { app, App } = bindings;
// Only one app object permitted. // Only one app object permitted.
export default app export default app;
let dockMenu: Electron.Menu | null = null let dockMenu: Electron.Menu | null = null;
// App is an EventEmitter. // App is an EventEmitter.
Object.setPrototypeOf(App.prototype, EventEmitter.prototype) Object.setPrototypeOf(App.prototype, EventEmitter.prototype);
EventEmitter.call(app as any) EventEmitter.call(app as any);
// Properties.
const nativeASGetter = app.isAccessibilitySupportEnabled;
const nativeASSetter = app.setAccessibilitySupportEnabled;
Object.defineProperty(App.prototype, 'accessibilitySupportEnabled', {
get: () => nativeASGetter.call(app),
set: (enabled) => nativeASSetter.call(app, enabled)
});
const nativeBCGetter = app.getBadgeCount;
const nativeBCSetter = app.setBadgeCount;
Object.defineProperty(App.prototype, 'badgeCount', {
get: () => nativeBCGetter.call(app),
set: (count) => nativeBCSetter.call(app, count)
});
const nativeNGetter = app.getName;
const nativeNSetter = app.setName;
Object.defineProperty(App.prototype, 'name', {
get: () => nativeNGetter.call(app),
set: (name) => nativeNSetter.call(app, name)
});
Object.assign(app, { Object.assign(app, {
commandLine: { commandLine: {
@@ -24,99 +47,94 @@ Object.assign(app, {
appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)), appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
appendArgument: (arg: string) => commandLine.appendArgument(String(arg)) appendArgument: (arg: string) => commandLine.appendArgument(String(arg))
} as Electron.CommandLine } as Electron.CommandLine
}) });
// we define this here because it'd be overly complicated to // we define this here because it'd be overly complicated to
// do in native land // do in native land
Object.defineProperty(app, 'applicationMenu', { Object.defineProperty(app, 'applicationMenu', {
get () { get () {
return Menu.getApplicationMenu() return Menu.getApplicationMenu();
}, },
set (menu: Electron.Menu | null) { set (menu: Electron.Menu | null) {
return Menu.setApplicationMenu(menu) return Menu.setApplicationMenu(menu);
} }
}) });
App.prototype.isPackaged = (() => { App.prototype.isPackaged = (() => {
const execFile = path.basename(process.execPath).toLowerCase() const execFile = path.basename(process.execPath).toLowerCase();
if (process.platform === 'win32') { if (process.platform === 'win32') {
return execFile !== 'electron.exe' return execFile !== 'electron.exe';
} }
return execFile !== 'electron' return execFile !== 'electron';
})() })();
app._setDefaultAppPaths = (packagePath) => { app._setDefaultAppPaths = (packagePath) => {
// Set the user path according to application's name. // Set the user path according to application's name.
app.setPath('userData', path.join(app.getPath('appData'), app.name!)) app.setPath('userData', path.join(app.getPath('appData'), app.name!));
app.setPath('userCache', path.join(app.getPath('cache'), app.name!)) app.setPath('userCache', path.join(app.getPath('cache'), app.name!));
app.setAppPath(packagePath) app.setAppPath(packagePath);
// Add support for --user-data-dir= // Add support for --user-data-dir=
if (app.commandLine.hasSwitch('user-data-dir')) { if (app.commandLine.hasSwitch('user-data-dir')) {
const userDataDir = app.commandLine.getSwitchValue('user-data-dir') const userDataDir = app.commandLine.getSwitchValue('user-data-dir');
if (path.isAbsolute(userDataDir)) app.setPath('userData', userDataDir) if (path.isAbsolute(userDataDir)) app.setPath('userData', userDataDir);
}
} }
};
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
const setDockMenu = app.dock!.setMenu const setDockMenu = app.dock!.setMenu;
app.dock!.setMenu = (menu) => { app.dock!.setMenu = (menu) => {
dockMenu = menu dockMenu = menu;
setDockMenu(menu) setDockMenu(menu);
} };
app.dock!.getMenu = () => dockMenu app.dock!.getMenu = () => dockMenu;
} }
if (process.platform === 'linux') { if (process.platform === 'linux') {
const patternVmRSS = /^VmRSS:\s*(\d+) kB$/m const patternVmRSS = /^VmRSS:\s*(\d+) kB$/m;
const patternVmHWM = /^VmHWM:\s*(\d+) kB$/m const patternVmHWM = /^VmHWM:\s*(\d+) kB$/m;
const getStatus = (pid: number) => { const getStatus = (pid: number) => {
try { try {
return fs.readFileSync(`/proc/${pid}/status`, 'utf8') return fs.readFileSync(`/proc/${pid}/status`, 'utf8');
} catch { } catch {
return '' return '';
}
} }
};
const getEntry = (file: string, pattern: RegExp) => { const getEntry = (file: string, pattern: RegExp) => {
const match = file.match(pattern) const match = file.match(pattern);
return match ? parseInt(match[1], 10) : 0 return match ? parseInt(match[1], 10) : 0;
} };
const getProcessMemoryInfo = (pid: number) => { const getProcessMemoryInfo = (pid: number) => {
const file = getStatus(pid) const file = getStatus(pid);
return { return {
workingSetSize: getEntry(file, patternVmRSS), workingSetSize: getEntry(file, patternVmRSS),
peakWorkingSetSize: getEntry(file, patternVmHWM) peakWorkingSetSize: getEntry(file, patternVmHWM)
} };
} };
const nativeFn = app.getAppMetrics const nativeFn = app.getAppMetrics;
app.getAppMetrics = () => { app.getAppMetrics = () => {
const metrics = nativeFn.call(app) const metrics = nativeFn.call(app);
for (const metric of metrics) { for (const metric of metrics) {
metric.memory = getProcessMemoryInfo(metric.pid) metric.memory = getProcessMemoryInfo(metric.pid);
} }
return metrics return metrics;
} };
} }
// Routes the events to webContents. // Routes the events to webContents.
const events = ['certificate-error', 'select-client-certificate'] const events = ['certificate-error', 'select-client-certificate'];
for (const name of events) { for (const name of events) {
app.on(name as 'certificate-error', (event, webContents, ...args: any[]) => { app.on(name as 'certificate-error', (event, webContents, ...args: any[]) => {
webContents.emit(name, event, ...args) webContents.emit(name, event, ...args);
}) });
} }
// Property Deprecations
deprecate.fnToProperty(App.prototype, 'accessibilitySupportEnabled', '_isAccessibilitySupportEnabled', '_setAccessibilitySupportEnabled')
deprecate.fnToProperty(App.prototype, 'badgeCount', '_getBadgeCount', '_setBadgeCount')
deprecate.fnToProperty(App.prototype, 'name', '_getName', '_setName')
// Wrappers for native classes. // Wrappers for native classes.
const { DownloadItem } = process.electronBinding('download_item') const { DownloadItem } = process.electronBinding('download_item');
Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype) Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype);

View File

@@ -1,7 +1,7 @@
'use strict' 'use strict';
if (process.platform === 'win32') { if (process.platform === 'win32') {
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win') module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win');
} else { } else {
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native') module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native');
} }

View File

@@ -1,10 +1,10 @@
'use strict' 'use strict';
const EventEmitter = require('events').EventEmitter const EventEmitter = require('events').EventEmitter;
const { autoUpdater, AutoUpdater } = process.electronBinding('auto_updater') const { autoUpdater, AutoUpdater } = process.electronBinding('auto_updater');
// AutoUpdater is an EventEmitter. // AutoUpdater is an EventEmitter.
Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype) Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype);
EventEmitter.call(autoUpdater) EventEmitter.call(autoUpdater);
module.exports = autoUpdater module.exports = autoUpdater;

View File

@@ -1,74 +1,74 @@
'use strict' 'use strict';
const { app } = require('electron') const { app } = require('electron');
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win') const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win');
class AutoUpdater extends EventEmitter { class AutoUpdater extends EventEmitter {
quitAndInstall () { quitAndInstall () {
if (!this.updateAvailable) { if (!this.updateAvailable) {
return this.emitError('No update available, can\'t quit and install') return this.emitError('No update available, can\'t quit and install');
} }
squirrelUpdate.processStart() squirrelUpdate.processStart();
app.quit() app.quit();
} }
getFeedURL () { getFeedURL () {
return this.updateURL return this.updateURL;
} }
setFeedURL (options) { setFeedURL (options) {
let updateURL let updateURL;
if (typeof options === 'object') { if (typeof options === 'object') {
if (typeof options.url === 'string') { if (typeof options.url === 'string') {
updateURL = options.url updateURL = options.url;
} else { } else {
throw new Error('Expected options object to contain a \'url\' string property in setFeedUrl call') throw new Error('Expected options object to contain a \'url\' string property in setFeedUrl call');
} }
} else if (typeof options === 'string') { } else if (typeof options === 'string') {
updateURL = options updateURL = options;
} else { } else {
throw new Error('Expected an options object with a \'url\' property to be provided') throw new Error('Expected an options object with a \'url\' property to be provided');
} }
this.updateURL = updateURL this.updateURL = updateURL;
} }
checkForUpdates () { checkForUpdates () {
if (!this.updateURL) { if (!this.updateURL) {
return this.emitError('Update URL is not set') return this.emitError('Update URL is not set');
} }
if (!squirrelUpdate.supported()) { if (!squirrelUpdate.supported()) {
return this.emitError('Can not find Squirrel') return this.emitError('Can not find Squirrel');
} }
this.emit('checking-for-update') this.emit('checking-for-update');
squirrelUpdate.checkForUpdate(this.updateURL, (error, update) => { squirrelUpdate.checkForUpdate(this.updateURL, (error, update) => {
if (error != null) { if (error != null) {
return this.emitError(error) return this.emitError(error);
} }
if (update == null) { if (update == null) {
return this.emit('update-not-available') return this.emit('update-not-available');
} }
this.updateAvailable = true this.updateAvailable = true;
this.emit('update-available') this.emit('update-available');
squirrelUpdate.update(this.updateURL, (error) => { squirrelUpdate.update(this.updateURL, (error) => {
if (error != null) { if (error != null) {
return this.emitError(error) return this.emitError(error);
} }
const { releaseNotes, version } = update const { releaseNotes, version } = update;
// Date is not available on Windows, so fake it. // Date is not available on Windows, so fake it.
const date = new Date() const date = new Date();
this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => { this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => {
this.quitAndInstall() this.quitAndInstall();
}) });
}) });
}) });
} }
// Private: Emit both error object and message, this is to keep compatibility // Private: Emit both error object and message, this is to keep compatibility
// with Old APIs. // with Old APIs.
emitError (message) { emitError (message) {
this.emit('error', new Error(message), message) this.emit('error', new Error(message), message);
} }
} }
module.exports = new AutoUpdater() module.exports = new AutoUpdater();

View File

@@ -1,24 +1,24 @@
'use strict' 'use strict';
const fs = require('fs') const fs = require('fs');
const path = require('path') const path = require('path');
const spawn = require('child_process').spawn const spawn = require('child_process').spawn;
// i.e. my-app/app-0.1.13/ // i.e. my-app/app-0.1.13/
const appFolder = path.dirname(process.execPath) const appFolder = path.dirname(process.execPath);
// i.e. my-app/Update.exe // i.e. my-app/Update.exe
const updateExe = path.resolve(appFolder, '..', 'Update.exe') const updateExe = path.resolve(appFolder, '..', 'Update.exe');
const exeName = path.basename(process.execPath) const exeName = path.basename(process.execPath);
let spawnedArgs = [] let spawnedArgs = [];
let spawnedProcess let spawnedProcess;
const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]) const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]);
// Spawn a command and invoke the callback when it completes with an error // Spawn a command and invoke the callback when it completes with an error
// and the output from standard out. // and the output from standard out.
const spawnUpdate = function (args, detached, callback) { const spawnUpdate = function (args, detached, callback) {
let error, errorEmitted, stderr, stdout let error, errorEmitted, stderr, stdout;
try { try {
// Ensure we don't spawn multiple squirrel processes // Ensure we don't spawn multiple squirrel processes
@@ -28,92 +28,92 @@ const spawnUpdate = function (args, detached, callback) {
if (spawnedProcess && !isSameArgs(args)) { if (spawnedProcess && !isSameArgs(args)) {
// Disabled for backwards compatibility: // Disabled for backwards compatibility:
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback(`AutoUpdater process with arguments ${args} is already running`) return callback(`AutoUpdater process with arguments ${args} is already running`);
} else if (!spawnedProcess) { } else if (!spawnedProcess) {
spawnedProcess = spawn(updateExe, args, { spawnedProcess = spawn(updateExe, args, {
detached: detached, detached: detached,
windowsHide: true windowsHide: true
}) });
spawnedArgs = args || [] spawnedArgs = args || [];
} }
} catch (error1) { } catch (error1) {
error = error1 error = error1;
// Shouldn't happen, but still guard it. // Shouldn't happen, but still guard it.
process.nextTick(function () { process.nextTick(function () {
return callback(error) return callback(error);
}) });
return return;
} }
stdout = '' stdout = '';
stderr = '' stderr = '';
spawnedProcess.stdout.on('data', (data) => { stdout += data }) spawnedProcess.stdout.on('data', (data) => { stdout += data; });
spawnedProcess.stderr.on('data', (data) => { stderr += data }) spawnedProcess.stderr.on('data', (data) => { stderr += data; });
errorEmitted = false errorEmitted = false;
spawnedProcess.on('error', (error) => { spawnedProcess.on('error', (error) => {
errorEmitted = true errorEmitted = true;
callback(error) callback(error);
}) });
return spawnedProcess.on('exit', function (code, signal) { return spawnedProcess.on('exit', function (code, signal) {
spawnedProcess = undefined spawnedProcess = undefined;
spawnedArgs = [] spawnedArgs = [];
// We may have already emitted an error. // We may have already emitted an error.
if (errorEmitted) { if (errorEmitted) {
return return;
} }
// Process terminated with error. // Process terminated with error.
if (code !== 0) { if (code !== 0) {
// Disabled for backwards compatibility: // Disabled for backwards compatibility:
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback(`Command failed: ${signal != null ? signal : code}\n${stderr}`) return callback(`Command failed: ${signal != null ? signal : code}\n${stderr}`);
} }
// Success. // Success.
callback(null, stdout) callback(null, stdout);
}) });
} };
// Start an instance of the installed app. // Start an instance of the installed app.
exports.processStart = function () { exports.processStart = function () {
return spawnUpdate(['--processStartAndWait', exeName], true, function () {}) return spawnUpdate(['--processStartAndWait', exeName], true, function () {});
} };
// Download the releases specified by the URL and write new results to stdout. // Download the releases specified by the URL and write new results to stdout.
exports.checkForUpdate = function (updateURL, callback) { exports.checkForUpdate = function (updateURL, callback) {
return spawnUpdate(['--checkForUpdate', updateURL], false, function (error, stdout) { return spawnUpdate(['--checkForUpdate', updateURL], false, function (error, stdout) {
let ref, ref1, update let ref, ref1, update;
if (error != null) { if (error != null) {
return callback(error) return callback(error);
} }
try { try {
// Last line of output is the JSON details about the releases // Last line of output is the JSON details about the releases
const json = stdout.trim().split('\n').pop() const json = stdout.trim().split('\n').pop();
update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0 update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0;
} catch { } catch {
// Disabled for backwards compatibility: // Disabled for backwards compatibility:
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback(`Invalid result:\n${stdout}`) return callback(`Invalid result:\n${stdout}`);
}
return callback(null, update)
})
} }
return callback(null, update);
});
};
// Update the application to the latest remote version specified by URL. // Update the application to the latest remote version specified by URL.
exports.update = function (updateURL, callback) { exports.update = function (updateURL, callback) {
return spawnUpdate(['--update', updateURL], false, callback) return spawnUpdate(['--update', updateURL], false, callback);
} };
// Is the Update.exe installed with the current application? // Is the Update.exe installed with the current application?
exports.supported = function () { exports.supported = function () {
try { try {
fs.accessSync(updateExe, fs.R_OK) fs.accessSync(updateExe, fs.R_OK);
return true return true;
} catch { } catch {
return false return false;
}
} }
};

View File

@@ -1,16 +1,16 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { BrowserView } = process.electronBinding('browser_view') const { BrowserView } = process.electronBinding('browser_view');
Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype) Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype);
BrowserView.fromWebContents = (webContents) => { BrowserView.fromWebContents = (webContents) => {
for (const view of BrowserView.getAllViews()) { for (const view of BrowserView.getAllViews()) {
if (view.webContents.equal(webContents)) return view if (view.webContents.equal(webContents)) return view;
} }
return null return null;
} };
module.exports = BrowserView module.exports = BrowserView;

View File

@@ -1,49 +1,49 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { WebContentsView, TopLevelWindow, deprecate } = electron const { WebContentsView, TopLevelWindow, deprecate } = electron;
const { BrowserWindow } = process.electronBinding('window') const { BrowserWindow } = process.electronBinding('window');
Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype) Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype);
BrowserWindow.prototype._init = function () { BrowserWindow.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
TopLevelWindow.prototype._init.call(this) TopLevelWindow.prototype._init.call(this);
// Avoid recursive require. // Avoid recursive require.
const { app } = electron const { app } = electron;
// Create WebContentsView. // Create WebContentsView.
this.setContentView(new WebContentsView(this.webContents)) this.setContentView(new WebContentsView(this.webContents));
const nativeSetBounds = this.setBounds const nativeSetBounds = this.setBounds;
this.setBounds = (bounds, ...opts) => { this.setBounds = (bounds, ...opts) => {
bounds = { bounds = {
...this.getBounds(), ...this.getBounds(),
...bounds ...bounds
} };
nativeSetBounds.call(this, bounds, ...opts) nativeSetBounds.call(this, bounds, ...opts);
} };
// window.resizeTo(...) // window.resizeTo(...)
// window.moveTo(...) // window.moveTo(...)
this.webContents.on('move', (event, size) => { this.webContents.on('move', (event, size) => {
this.setBounds(size) this.setBounds(size);
}) });
// Hide the auto-hide menu when webContents is focused. // Hide the auto-hide menu when webContents is focused.
this.webContents.on('activate', () => { this.webContents.on('activate', () => {
if (process.platform !== 'darwin' && this.autoHideMenuBar && this.isMenuBarVisible()) { if (process.platform !== 'darwin' && this.autoHideMenuBar && this.isMenuBarVisible()) {
this.setMenuBarVisibility(false) this.setMenuBarVisibility(false);
} }
}) });
// Change window title to page title. // Change window title to page title.
this.webContents.on('page-title-updated', (event, title, ...args) => { this.webContents.on('page-title-updated', (event, title, ...args) => {
// Route the event to BrowserWindow. // Route the event to BrowserWindow.
this.emit('page-title-updated', event, title, ...args) this.emit('page-title-updated', event, title, ...args);
if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title) if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title);
}) });
// Sometimes the webContents doesn't get focus when window is shown, so we // Sometimes the webContents doesn't get focus when window is shown, so we
// have to force focusing on webContents in this case. The safest way is to // have to force focusing on webContents in this case. The safest way is to
@@ -54,144 +54,172 @@ BrowserWindow.prototype._init = function () {
// Finder, we still do it on all platforms in case of other bugs we don't // Finder, we still do it on all platforms in case of other bugs we don't
// know. // know.
this.webContents.once('load-url', function () { this.webContents.once('load-url', function () {
this.focus() this.focus();
}) });
// Redirect focus/blur event to app instance too. // Redirect focus/blur event to app instance too.
this.on('blur', (event) => { this.on('blur', (event) => {
app.emit('browser-window-blur', event, this) app.emit('browser-window-blur', event, this);
}) });
this.on('focus', (event) => { this.on('focus', (event) => {
app.emit('browser-window-focus', event, this) app.emit('browser-window-focus', event, this);
}) });
// Subscribe to visibilityState changes and pass to renderer process. // Subscribe to visibilityState changes and pass to renderer process.
let isVisible = this.isVisible() && !this.isMinimized() let isVisible = this.isVisible() && !this.isMinimized();
const visibilityChanged = () => { const visibilityChanged = () => {
const newState = this.isVisible() && !this.isMinimized() const newState = this.isVisible() && !this.isMinimized();
if (isVisible !== newState) { if (isVisible !== newState) {
isVisible = newState isVisible = newState;
const visibilityState = isVisible ? 'visible' : 'hidden' const visibilityState = isVisible ? 'visible' : 'hidden';
this.webContents.emit('-window-visibility-change', visibilityState) this.webContents.emit('-window-visibility-change', visibilityState);
}
} }
};
const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'] const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'];
for (const event of visibilityEvents) { for (const event of visibilityEvents) {
this.on(event, visibilityChanged) this.on(event, visibilityChanged);
} }
// Notify the creation of the window. // Notify the creation of the window.
const event = process.electronBinding('event').createEmpty() const event = process.electronBinding('event').createEmpty();
app.emit('browser-window-created', event, this) app.emit('browser-window-created', event, this);
Object.defineProperty(this, 'devToolsWebContents', { Object.defineProperty(this, 'devToolsWebContents', {
enumerable: true, enumerable: true,
configurable: false, configurable: false,
get () { get () {
return this.webContents.devToolsWebContents return this.webContents.devToolsWebContents;
}
})
} }
});
// Properties
Object.defineProperty(this, 'autoHideMenuBar', {
get: () => this.isMenuBarAutoHide(),
set: (autoHide) => this.setAutoHideMenuBar(autoHide)
});
Object.defineProperty(this, 'minimizable', {
get: () => this.isMinimizable(),
set: (min) => this.setMinimizable(min)
});
Object.defineProperty(this, 'maximizable', {
get: () => this.isMaximizable(),
set: (max) => this.setMaximizable(max)
});
Object.defineProperty(this, 'resizable', {
get: () => this.isResizable(),
set: (res) => this.setResizable(res)
});
Object.defineProperty(this, 'fullScreenable', {
get: () => this.isFullScreenable(),
set: (full) => this.setFullScreenable(full)
});
Object.defineProperty(this, 'closable', {
get: () => this.isClosable(),
set: (close) => this.setClosable(close)
});
Object.defineProperty(this, 'movable', {
get: () => this.isMovable(),
set: (move) => this.setMovable(move)
});
};
const isBrowserWindow = (win) => { const isBrowserWindow = (win) => {
return win && win.constructor.name === 'BrowserWindow' return win && win.constructor.name === 'BrowserWindow';
} };
BrowserWindow.fromId = (id) => { BrowserWindow.fromId = (id) => {
const win = TopLevelWindow.fromId(id) const win = TopLevelWindow.fromId(id);
return isBrowserWindow(win) ? win : null return isBrowserWindow(win) ? win : null;
} };
BrowserWindow.getAllWindows = () => { BrowserWindow.getAllWindows = () => {
return TopLevelWindow.getAllWindows().filter(isBrowserWindow) return TopLevelWindow.getAllWindows().filter(isBrowserWindow);
} };
BrowserWindow.getFocusedWindow = () => { BrowserWindow.getFocusedWindow = () => {
for (const window of BrowserWindow.getAllWindows()) { for (const window of BrowserWindow.getAllWindows()) {
if (window.isFocused() || window.isDevToolsFocused()) return window if (window.isFocused() || window.isDevToolsFocused()) return window;
}
return null
} }
return null;
};
BrowserWindow.fromWebContents = (webContents) => { BrowserWindow.fromWebContents = (webContents) => {
for (const window of BrowserWindow.getAllWindows()) { for (const window of BrowserWindow.getAllWindows()) {
if (window.webContents && window.webContents.equal(webContents)) return window if (window.webContents && window.webContents.equal(webContents)) return window;
} }
return null return null;
} };
BrowserWindow.fromBrowserView = (browserView) => { BrowserWindow.fromBrowserView = (browserView) => {
for (const window of BrowserWindow.getAllWindows()) { for (const window of BrowserWindow.getAllWindows()) {
if (window.getBrowserView() === browserView) return window if (window.getBrowserView() === browserView) return window;
} }
return null return null;
} };
// Helpers. // Helpers.
Object.assign(BrowserWindow.prototype, { Object.assign(BrowserWindow.prototype, {
loadURL (...args) { loadURL (...args) {
return this.webContents.loadURL(...args) return this.webContents.loadURL(...args);
}, },
getURL (...args) { getURL (...args) {
return this.webContents.getURL() return this.webContents.getURL();
}, },
loadFile (...args) { loadFile (...args) {
return this.webContents.loadFile(...args) return this.webContents.loadFile(...args);
}, },
reload (...args) { reload (...args) {
return this.webContents.reload(...args) return this.webContents.reload(...args);
}, },
send (...args) { send (...args) {
return this.webContents.send(...args) return this.webContents.send(...args);
}, },
openDevTools (...args) { openDevTools (...args) {
return this.webContents.openDevTools(...args) return this.webContents.openDevTools(...args);
}, },
closeDevTools () { closeDevTools () {
return this.webContents.closeDevTools() return this.webContents.closeDevTools();
}, },
isDevToolsOpened () { isDevToolsOpened () {
return this.webContents.isDevToolsOpened() return this.webContents.isDevToolsOpened();
}, },
isDevToolsFocused () { isDevToolsFocused () {
return this.webContents.isDevToolsFocused() return this.webContents.isDevToolsFocused();
}, },
toggleDevTools () { toggleDevTools () {
return this.webContents.toggleDevTools() return this.webContents.toggleDevTools();
}, },
inspectElement (...args) { inspectElement (...args) {
return this.webContents.inspectElement(...args) return this.webContents.inspectElement(...args);
}, },
inspectSharedWorker () { inspectSharedWorker () {
return this.webContents.inspectSharedWorker() return this.webContents.inspectSharedWorker();
}, },
inspectServiceWorker () { inspectServiceWorker () {
return this.webContents.inspectServiceWorker() return this.webContents.inspectServiceWorker();
}, },
showDefinitionForSelection () { showDefinitionForSelection () {
return this.webContents.showDefinitionForSelection() return this.webContents.showDefinitionForSelection();
}, },
capturePage (...args) { capturePage (...args) {
return this.webContents.capturePage(...args) return this.webContents.capturePage(...args);
}, },
setTouchBar (touchBar) { setTouchBar (touchBar) {
electron.TouchBar._setOnWindow(touchBar, this) electron.TouchBar._setOnWindow(touchBar, this);
}, },
setBackgroundThrottling (allowed) { setBackgroundThrottling (allowed) {
this.webContents.setBackgroundThrottling(allowed) this.webContents.setBackgroundThrottling(allowed);
} }
}) });
// Deprecations module.exports = BrowserWindow;
deprecate.fnToProperty(BrowserWindow.prototype, 'autoHideMenuBar', '_isMenuBarAutoHide', '_setAutoHideMenuBar')
deprecate.fnToProperty(BrowserWindow.prototype, 'minimizable', '_isMinimizable', '_setMinimizable')
deprecate.fnToProperty(BrowserWindow.prototype, 'maximizable', '_isMaximizable', '_setMaximizable')
deprecate.fnToProperty(BrowserWindow.prototype, 'resizable', '_isResizable', '_setResizable')
deprecate.fnToProperty(BrowserWindow.prototype, 'fullScreenable', '_isFullScreenable', '_setFullScreenable')
deprecate.fnToProperty(BrowserWindow.prototype, 'closable', '_isClosable', '_setClosable')
deprecate.fnToProperty(BrowserWindow.prototype, 'movable', '_isMovable', '_setMovable')
module.exports = BrowserWindow

View File

@@ -1,2 +1,2 @@
'use strict' 'use strict';
module.exports = process.electronBinding('content_tracing') module.exports = process.electronBinding('content_tracing');

View File

@@ -1,12 +1,12 @@
'use strict' 'use strict';
const CrashReporter = require('@electron/internal/common/crash-reporter') const CrashReporter = require('@electron/internal/common/crash-reporter');
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init') const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init');
class CrashReporterMain extends CrashReporter { class CrashReporterMain extends CrashReporter {
init (options) { init (options) {
return crashReporterInit(options) return crashReporterInit(options);
} }
} }
module.exports = new CrashReporterMain() module.exports = new CrashReporterMain();

View File

@@ -1,13 +1,13 @@
'use strict' 'use strict';
const { app, BrowserWindow, deprecate } = require('electron') const { app, BrowserWindow, deprecate } = require('electron');
const binding = process.electronBinding('dialog') const binding = process.electronBinding('dialog');
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const DialogType = { const DialogType = {
OPEN: 'OPEN', OPEN: 'OPEN',
SAVE: 'SAVE' SAVE: 'SAVE'
} };
const saveFileDialogProperties = { const saveFileDialogProperties = {
createDirectory: 1 << 0, createDirectory: 1 << 0,
@@ -15,7 +15,7 @@ const saveFileDialogProperties = {
treatPackageAsDirectory: 1 << 2, treatPackageAsDirectory: 1 << 2,
showOverwriteConfirmation: 1 << 3, showOverwriteConfirmation: 1 << 3,
dontAddToRecent: 1 << 4 dontAddToRecent: 1 << 4
} };
const openFileDialogProperties = { const openFileDialogProperties = {
openFile: 1 << 0, openFile: 1 << 0,
@@ -27,15 +27,15 @@ const openFileDialogProperties = {
noResolveAliases: 1 << 6, // macOS noResolveAliases: 1 << 6, // macOS
treatPackageAsDirectory: 1 << 7, // macOS treatPackageAsDirectory: 1 << 7, // macOS
dontAddToRecent: 1 << 8 // Windows dontAddToRecent: 1 << 8 // Windows
} };
const normalizeAccessKey = (text) => { const normalizeAccessKey = (text) => {
if (typeof text !== 'string') return text if (typeof text !== 'string') return text;
// macOS does not have access keys so remove single ampersands // macOS does not have access keys so remove single ampersands
// and replace double ampersands with a single ampersand // and replace double ampersands with a single ampersand
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
return text.replace(/&(&?)/g, '$1') return text.replace(/&(&?)/g, '$1');
} }
// Linux uses a single underscore as an access key prefix so escape // Linux uses a single underscore as an access key prefix so escape
@@ -44,41 +44,41 @@ const normalizeAccessKey = (text) => {
// a single underscore // a single underscore
if (process.platform === 'linux') { if (process.platform === 'linux') {
return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => { return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
if (after === '&') return after if (after === '&') return after;
return `_${after}` return `_${after}`;
}) });
} }
return text return text;
} };
const checkAppInitialized = function () { const checkAppInitialized = function () {
if (!app.isReady()) { if (!app.isReady()) {
throw new Error('dialog module can only be used after app is ready') throw new Error('dialog module can only be used after app is ready');
}
} }
};
const setupDialogProperties = (type, properties) => { const setupDialogProperties = (type, properties) => {
const dialogPropertiesTypes = (type === DialogType.OPEN) ? openFileDialogProperties : saveFileDialogProperties const dialogPropertiesTypes = (type === DialogType.OPEN) ? openFileDialogProperties : saveFileDialogProperties;
let dialogProperties = 0 let dialogProperties = 0;
for (const prop in dialogPropertiesTypes) { for (const prop in dialogPropertiesTypes) {
if (properties.includes(prop)) { if (properties.includes(prop)) {
dialogProperties |= dialogPropertiesTypes[prop] dialogProperties |= dialogPropertiesTypes[prop];
} }
} }
return dialogProperties return dialogProperties;
} };
const saveDialog = (sync, window, options) => { const saveDialog = (sync, window, options) => {
checkAppInitialized() checkAppInitialized();
if (window && window.constructor !== BrowserWindow) { if (window && window.constructor !== BrowserWindow) {
options = window options = window;
window = null window = null;
} }
if (options == null) options = { title: 'Save' } if (options == null) options = { title: 'Save' };
const { const {
buttonLabel = '', buttonLabel = '',
@@ -90,33 +90,33 @@ const saveDialog = (sync, window, options) => {
securityScopedBookmarks = false, securityScopedBookmarks = false,
nameFieldLabel = '', nameFieldLabel = '',
showsTagField = true showsTagField = true
} = options } = options;
if (typeof title !== 'string') throw new TypeError('Title must be a string') if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string') if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string') if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
if (typeof message !== 'string') throw new TypeError('Message must be a string') if (typeof message !== 'string') throw new TypeError('Message must be a string');
if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string') if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string');
const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window } const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window };
settings.properties = setupDialogProperties(DialogType.SAVE, properties) settings.properties = setupDialogProperties(DialogType.SAVE, properties);
return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings) return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings);
} };
const openDialog = (sync, window, options) => { const openDialog = (sync, window, options) => {
checkAppInitialized() checkAppInitialized();
if (window && window.constructor !== BrowserWindow) { if (window && window.constructor !== BrowserWindow) {
options = window options = window;
window = null window = null;
} }
if (options == null) { if (options == null) {
options = { options = {
title: 'Open', title: 'Open',
properties: ['openFile'] properties: ['openFile']
} };
} }
const { const {
@@ -127,33 +127,33 @@ const openDialog = (sync, window, options) => {
title = '', title = '',
message = '', message = '',
securityScopedBookmarks = false securityScopedBookmarks = false
} = options } = options;
if (!Array.isArray(properties)) throw new TypeError('Properties must be an array') if (!Array.isArray(properties)) throw new TypeError('Properties must be an array');
if (typeof title !== 'string') throw new TypeError('Title must be a string') if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string') if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string') if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
if (typeof message !== 'string') throw new TypeError('Message must be a string') if (typeof message !== 'string') throw new TypeError('Message must be a string');
const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window } const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window };
settings.properties = setupDialogProperties(DialogType.OPEN, properties) settings.properties = setupDialogProperties(DialogType.OPEN, properties);
return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings) return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings);
} };
const messageBox = (sync, window, options) => { const messageBox = (sync, window, options) => {
checkAppInitialized() checkAppInitialized();
if (window && window.constructor !== BrowserWindow) { if (window && window.constructor !== BrowserWindow) {
options = window options = window;
window = null window = null;
} }
if (options == null) options = { type: 'none' } if (options == null) options = { type: 'none' };
const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'];
const messageBoxOptions = { noLink: 1 << 0 } const messageBoxOptions = { noLink: 1 << 0 };
let { let {
buttons = [], buttons = [],
@@ -167,32 +167,32 @@ const messageBox = (sync, window, options) => {
message = '', message = '',
title = '', title = '',
type = 'none' type = 'none'
} = options } = options;
const messageBoxType = messageBoxTypes.indexOf(type) const messageBoxType = messageBoxTypes.indexOf(type);
if (messageBoxType === -1) throw new TypeError('Invalid message box type') if (messageBoxType === -1) throw new TypeError('Invalid message box type');
if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array') if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array');
if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey) if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey);
if (typeof title !== 'string') throw new TypeError('Title must be a string') if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean') if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean');
if (typeof message !== 'string') throw new TypeError('Message must be a string') if (typeof message !== 'string') throw new TypeError('Message must be a string');
if (typeof detail !== 'string') throw new TypeError('Detail must be a string') if (typeof detail !== 'string') throw new TypeError('Detail must be a string');
if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string') if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string');
checkboxChecked = !!checkboxChecked checkboxChecked = !!checkboxChecked;
if (checkboxChecked && !checkboxLabel) { if (checkboxChecked && !checkboxLabel) {
throw new Error('checkboxChecked requires that checkboxLabel also be passed') throw new Error('checkboxChecked requires that checkboxLabel also be passed');
} }
// Choose a default button to get selected when dialog is cancelled. // Choose a default button to get selected when dialog is cancelled.
if (cancelId == null) { if (cancelId == null) {
// If the defaultId is set to 0, ensure the cancel button is a different index (1) // If the defaultId is set to 0, ensure the cancel button is a different index (1)
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0 cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0;
for (let i = 0; i < buttons.length; i++) { for (let i = 0; i < buttons.length; i++) {
const text = buttons[i].toLowerCase() const text = buttons[i].toLowerCase();
if (text === 'cancel' || text === 'no') { if (text === 'cancel' || text === 'no') {
cancelId = i cancelId = i;
break break;
} }
} }
} }
@@ -210,57 +210,57 @@ const messageBox = (sync, window, options) => {
checkboxLabel, checkboxLabel,
checkboxChecked, checkboxChecked,
icon icon
} };
if (sync) { if (sync) {
return binding.showMessageBoxSync(settings) return binding.showMessageBoxSync(settings);
} else { } else {
return binding.showMessageBox(settings) return binding.showMessageBox(settings);
}
} }
};
module.exports = { module.exports = {
showOpenDialog: function (window, options) { showOpenDialog: function (window, options) {
return openDialog(false, window, options) return openDialog(false, window, options);
}, },
showOpenDialogSync: function (window, options) { showOpenDialogSync: function (window, options) {
return openDialog(true, window, options) return openDialog(true, window, options);
}, },
showSaveDialog: function (window, options) { showSaveDialog: function (window, options) {
return saveDialog(false, window, options) return saveDialog(false, window, options);
}, },
showSaveDialogSync: function (window, options) { showSaveDialogSync: function (window, options) {
return saveDialog(true, window, options) return saveDialog(true, window, options);
}, },
showMessageBox: function (window, options) { showMessageBox: function (window, options) {
return messageBox(false, window, options) return messageBox(false, window, options);
}, },
showMessageBoxSync: function (window, options) { showMessageBoxSync: function (window, options) {
return messageBox(true, window, options) return messageBox(true, window, options);
}, },
showErrorBox: function (...args) { showErrorBox: function (...args) {
return binding.showErrorBox(...args) return binding.showErrorBox(...args);
}, },
showCertificateTrustDialog: function (window, options) { showCertificateTrustDialog: function (window, options) {
if (window && window.constructor !== BrowserWindow) options = window if (window && window.constructor !== BrowserWindow) options = window;
if (options == null || typeof options !== 'object') { if (options == null || typeof options !== 'object') {
throw new TypeError('options must be an object') throw new TypeError('options must be an object');
} }
const { certificate, message = '' } = options const { certificate, message = '' } = options;
if (certificate == null || typeof certificate !== 'object') { if (certificate == null || typeof certificate !== 'object') {
throw new TypeError('certificate must be an object') throw new TypeError('certificate must be an object');
} }
if (typeof message !== 'string') throw new TypeError('message must be a string') if (typeof message !== 'string') throw new TypeError('message must be a string');
return binding.showCertificateTrustDialog(window, certificate, message) return binding.showCertificateTrustDialog(window, certificate, message);
}
} }
};

View File

@@ -1,8 +1,8 @@
import { defineProperties } from '@electron/internal/common/define-properties' import { defineProperties } from '@electron/internal/common/define-properties';
import { commonModuleList } from '@electron/internal/common/api/module-list' import { commonModuleList } from '@electron/internal/common/api/module-list';
import { browserModuleList } from '@electron/internal/browser/api/module-list' import { browserModuleList } from '@electron/internal/browser/api/module-list';
module.exports = {} module.exports = {};
defineProperties(module.exports, commonModuleList) defineProperties(module.exports, commonModuleList);
defineProperties(module.exports, browserModuleList) defineProperties(module.exports, browserModuleList);

View File

@@ -1,3 +1,3 @@
'use strict' 'use strict';
module.exports = process.electronBinding('global_shortcut').globalShortcut module.exports = process.electronBinding('global_shortcut').globalShortcut;

View File

@@ -1,22 +1,22 @@
'use strict' 'use strict';
const { deprecate } = require('electron') const { deprecate } = require('electron');
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { inAppPurchase, InAppPurchase } = process.electronBinding('in_app_purchase') const { inAppPurchase, InAppPurchase } = process.electronBinding('in_app_purchase');
// inAppPurchase is an EventEmitter. // inAppPurchase is an EventEmitter.
Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype) Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype);
EventEmitter.call(inAppPurchase) EventEmitter.call(inAppPurchase);
module.exports = inAppPurchase module.exports = inAppPurchase;
} else { } else {
module.exports = { module.exports = {
purchaseProduct: (productID, quantity, callback) => { purchaseProduct: (productID, quantity, callback) => {
throw new Error('The inAppPurchase module can only be used on macOS') throw new Error('The inAppPurchase module can only be used on macOS');
}, },
canMakePayments: () => false, canMakePayments: () => false,
getReceiptURL: () => '' getReceiptURL: () => ''
} };
} }

View File

@@ -1,8 +1,8 @@
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl' import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
const ipcMain = new IpcMainImpl() const ipcMain = new IpcMainImpl();
// Do not throw exception when channel name is "error". // Do not throw exception when channel name is "error".
ipcMain.on('error', () => {}) ipcMain.on('error', () => {});
export default ipcMain export default ipcMain;

View File

@@ -1,15 +1,15 @@
'use strict' 'use strict';
const { app } = require('electron') const { app } = require('electron');
const isMac = process.platform === 'darwin' const isMac = process.platform === 'darwin';
const isWindows = process.platform === 'win32' const isWindows = process.platform === 'win32';
const isLinux = process.platform === 'linux' const isLinux = process.platform === 'linux';
const roles = { const roles = {
about: { about: {
get label () { get label () {
return isLinux ? 'About' : `About ${app.name}` return isLinux ? 'About' : `About ${app.name}`;
} }
}, },
close: { close: {
@@ -38,7 +38,7 @@ const roles = {
accelerator: 'Shift+CmdOrCtrl+R', accelerator: 'Shift+CmdOrCtrl+R',
nonNativeMacOSRole: true, nonNativeMacOSRole: true,
windowMethod: (window) => { windowMethod: (window) => {
window.webContents.reloadIgnoringCache() window.webContents.reloadIgnoringCache();
} }
}, },
front: { front: {
@@ -49,7 +49,7 @@ const roles = {
}, },
hide: { hide: {
get label () { get label () {
return `Hide ${app.name}` return `Hide ${app.name}`;
}, },
accelerator: 'Command+H' accelerator: 'Command+H'
}, },
@@ -77,9 +77,9 @@ const roles = {
quit: { quit: {
get label () { get label () {
switch (process.platform) { switch (process.platform) {
case 'darwin': return `Quit ${app.name}` case 'darwin': return `Quit ${app.name}`;
case 'win32': return 'Exit' case 'win32': return 'Exit';
default: return 'Quit' default: return 'Quit';
} }
}, },
accelerator: isWindows ? undefined : 'CommandOrControl+Q', accelerator: isWindows ? undefined : 'CommandOrControl+Q',
@@ -101,7 +101,7 @@ const roles = {
accelerator: 'CommandOrControl+0', accelerator: 'CommandOrControl+0',
nonNativeMacOSRole: true, nonNativeMacOSRole: true,
webContentsMethod: (webContents) => { webContentsMethod: (webContents) => {
webContents.zoomLevel = 0 webContents.zoomLevel = 0;
} }
}, },
selectall: { selectall: {
@@ -134,7 +134,7 @@ const roles = {
label: 'Toggle Full Screen', label: 'Toggle Full Screen',
accelerator: isMac ? 'Control+Command+F' : 'F11', accelerator: isMac ? 'Control+Command+F' : 'F11',
windowMethod: (window) => { windowMethod: (window) => {
window.setFullScreen(!window.isFullScreen()) window.setFullScreen(!window.isFullScreen());
} }
}, },
undo: { undo: {
@@ -156,7 +156,7 @@ const roles = {
accelerator: 'CommandOrControl+Plus', accelerator: 'CommandOrControl+Plus',
nonNativeMacOSRole: true, nonNativeMacOSRole: true,
webContentsMethod: (webContents) => { webContentsMethod: (webContents) => {
webContents.zoomLevel += 0.5 webContents.zoomLevel += 0.5;
} }
}, },
zoomout: { zoomout: {
@@ -164,13 +164,13 @@ const roles = {
accelerator: 'CommandOrControl+-', accelerator: 'CommandOrControl+-',
nonNativeMacOSRole: true, nonNativeMacOSRole: true,
webContentsMethod: (webContents) => { webContentsMethod: (webContents) => {
webContents.zoomLevel -= 0.5 webContents.zoomLevel -= 0.5;
} }
}, },
// App submenu should be used for Mac only // App submenu should be used for Mac only
appmenu: { appmenu: {
get label () { get label () {
return app.name return app.name;
}, },
submenu: [ submenu: [
{ role: 'about' }, { role: 'about' },
@@ -249,71 +249,71 @@ const roles = {
]) ])
] ]
} }
} };
exports.roleList = roles exports.roleList = roles;
const canExecuteRole = (role) => { const canExecuteRole = (role) => {
if (!roles.hasOwnProperty(role)) return false if (!roles.hasOwnProperty(role)) return false;
if (!isMac) return true if (!isMac) return true;
// macOS handles all roles natively except for a few // macOS handles all roles natively except for a few
return roles[role].nonNativeMacOSRole return roles[role].nonNativeMacOSRole;
} };
exports.getDefaultLabel = (role) => { exports.getDefaultLabel = (role) => {
return roles.hasOwnProperty(role) ? roles[role].label : '' return roles.hasOwnProperty(role) ? roles[role].label : '';
} };
exports.getDefaultAccelerator = (role) => { exports.getDefaultAccelerator = (role) => {
if (roles.hasOwnProperty(role)) return roles[role].accelerator if (roles.hasOwnProperty(role)) return roles[role].accelerator;
} };
exports.shouldRegisterAccelerator = (role) => { exports.shouldRegisterAccelerator = (role) => {
const hasRoleRegister = roles.hasOwnProperty(role) && roles[role].registerAccelerator !== undefined const hasRoleRegister = roles.hasOwnProperty(role) && roles[role].registerAccelerator !== undefined;
return hasRoleRegister ? roles[role].registerAccelerator : true return hasRoleRegister ? roles[role].registerAccelerator : true;
} };
exports.getDefaultSubmenu = (role) => { exports.getDefaultSubmenu = (role) => {
if (!roles.hasOwnProperty(role)) return if (!roles.hasOwnProperty(role)) return;
let { submenu } = roles[role] let { submenu } = roles[role];
// remove null items from within the submenu // remove null items from within the submenu
if (Array.isArray(submenu)) { if (Array.isArray(submenu)) {
submenu = submenu.filter((item) => item != null) submenu = submenu.filter((item) => item != null);
} }
return submenu return submenu;
} };
exports.execute = (role, focusedWindow, focusedWebContents) => { exports.execute = (role, focusedWindow, focusedWebContents) => {
if (!canExecuteRole(role)) return false if (!canExecuteRole(role)) return false;
const { appMethod, webContentsMethod, windowMethod } = roles[role] const { appMethod, webContentsMethod, windowMethod } = roles[role];
if (appMethod) { if (appMethod) {
app[appMethod]() app[appMethod]();
return true return true;
} }
if (windowMethod && focusedWindow != null) { if (windowMethod && focusedWindow != null) {
if (typeof windowMethod === 'function') { if (typeof windowMethod === 'function') {
windowMethod(focusedWindow) windowMethod(focusedWindow);
} else { } else {
focusedWindow[windowMethod]() focusedWindow[windowMethod]();
} }
return true return true;
} }
if (webContentsMethod && focusedWebContents != null) { if (webContentsMethod && focusedWebContents != null) {
if (typeof webContentsMethod === 'function') { if (typeof webContentsMethod === 'function') {
webContentsMethod(focusedWebContents) webContentsMethod(focusedWebContents);
} else { } else {
focusedWebContents[webContentsMethod]() focusedWebContents[webContentsMethod]();
} }
return true return true;
} }
return false return false;
} };

View File

@@ -1,87 +1,87 @@
'use strict' 'use strict';
const roles = require('@electron/internal/browser/api/menu-item-roles') const roles = require('@electron/internal/browser/api/menu-item-roles');
let nextCommandId = 0 let nextCommandId = 0;
const MenuItem = function (options) { const MenuItem = function (options) {
const { Menu } = require('electron') const { Menu } = require('electron');
// Preserve extra fields specified by user // Preserve extra fields specified by user
for (const key in options) { for (const key in options) {
if (!(key in this)) this[key] = options[key] if (!(key in this)) this[key] = options[key];
} }
if (typeof this.role === 'string' || this.role instanceof String) { if (typeof this.role === 'string' || this.role instanceof String) {
this.role = this.role.toLowerCase() this.role = this.role.toLowerCase();
} }
this.submenu = this.submenu || roles.getDefaultSubmenu(this.role) this.submenu = this.submenu || roles.getDefaultSubmenu(this.role);
if (this.submenu != null && this.submenu.constructor !== Menu) { if (this.submenu != null && this.submenu.constructor !== Menu) {
this.submenu = Menu.buildFromTemplate(this.submenu) this.submenu = Menu.buildFromTemplate(this.submenu);
} }
if (this.type == null && this.submenu != null) { if (this.type == null && this.submenu != null) {
this.type = 'submenu' this.type = 'submenu';
} }
if (this.type === 'submenu' && (this.submenu == null || this.submenu.constructor !== Menu)) { if (this.type === 'submenu' && (this.submenu == null || this.submenu.constructor !== Menu)) {
throw new Error('Invalid submenu') throw new Error('Invalid submenu');
} }
this.overrideReadOnlyProperty('type', 'normal') this.overrideReadOnlyProperty('type', 'normal');
this.overrideReadOnlyProperty('role') this.overrideReadOnlyProperty('role');
this.overrideReadOnlyProperty('accelerator') this.overrideReadOnlyProperty('accelerator');
this.overrideReadOnlyProperty('icon') this.overrideReadOnlyProperty('icon');
this.overrideReadOnlyProperty('submenu') this.overrideReadOnlyProperty('submenu');
this.overrideProperty('label', roles.getDefaultLabel(this.role)) this.overrideProperty('label', roles.getDefaultLabel(this.role));
this.overrideProperty('sublabel', '') this.overrideProperty('sublabel', '');
this.overrideProperty('toolTip', '') this.overrideProperty('toolTip', '');
this.overrideProperty('enabled', true) this.overrideProperty('enabled', true);
this.overrideProperty('visible', true) this.overrideProperty('visible', true);
this.overrideProperty('checked', false) this.overrideProperty('checked', false);
this.overrideProperty('acceleratorWorksWhenHidden', true) this.overrideProperty('acceleratorWorksWhenHidden', true);
this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role)) this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role));
if (!MenuItem.types.includes(this.type)) { if (!MenuItem.types.includes(this.type)) {
throw new Error(`Unknown menu item type: ${this.type}`) throw new Error(`Unknown menu item type: ${this.type}`);
} }
this.overrideReadOnlyProperty('commandId', ++nextCommandId) this.overrideReadOnlyProperty('commandId', ++nextCommandId);
const click = options.click const click = options.click;
this.click = (event, focusedWindow, focusedWebContents) => { this.click = (event, focusedWindow, focusedWebContents) => {
// Manually flip the checked flags when clicked. // Manually flip the checked flags when clicked.
if (this.type === 'checkbox' || this.type === 'radio') { if (this.type === 'checkbox' || this.type === 'radio') {
this.checked = !this.checked this.checked = !this.checked;
} }
if (!roles.execute(this.role, focusedWindow, focusedWebContents)) { if (!roles.execute(this.role, focusedWindow, focusedWebContents)) {
if (typeof click === 'function') { if (typeof click === 'function') {
click(this, focusedWindow, event) click(this, focusedWindow, event);
} else if (typeof this.selector === 'string' && process.platform === 'darwin') { } else if (typeof this.selector === 'string' && process.platform === 'darwin') {
Menu.sendActionToFirstResponder(this.selector) Menu.sendActionToFirstResponder(this.selector);
}
}
} }
} }
};
};
MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'] MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'];
MenuItem.prototype.getDefaultRoleAccelerator = function () { MenuItem.prototype.getDefaultRoleAccelerator = function () {
return roles.getDefaultAccelerator(this.role) return roles.getDefaultAccelerator(this.role);
} };
MenuItem.prototype.overrideProperty = function (name, defaultValue = null) { MenuItem.prototype.overrideProperty = function (name, defaultValue = null) {
if (this[name] == null) { if (this[name] == null) {
this[name] = defaultValue this[name] = defaultValue;
}
} }
};
MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) { MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) {
this.overrideProperty(name, defaultValue) this.overrideProperty(name, defaultValue);
Object.defineProperty(this, name, { Object.defineProperty(this, name, {
enumerable: true, enumerable: true,
writable: false, writable: false,
value: this[name] value: this[name]
}) });
} };
module.exports = MenuItem module.exports = MenuItem;

View File

@@ -1,41 +1,41 @@
'use strict' 'use strict';
function splitArray (arr, predicate) { function splitArray (arr, predicate) {
const result = arr.reduce((multi, item) => { const result = arr.reduce((multi, item) => {
const current = multi[multi.length - 1] const current = multi[multi.length - 1];
if (predicate(item)) { if (predicate(item)) {
if (current.length > 0) multi.push([]) if (current.length > 0) multi.push([]);
} else { } else {
current.push(item) current.push(item);
} }
return multi return multi;
}, [[]]) }, [[]]);
if (result[result.length - 1].length === 0) { if (result[result.length - 1].length === 0) {
return result.slice(0, result.length - 1) return result.slice(0, result.length - 1);
} }
return result return result;
} }
function joinArrays (arrays, joinIDs) { function joinArrays (arrays, joinIDs) {
return arrays.reduce((joined, arr, i) => { return arrays.reduce((joined, arr, i) => {
if (i > 0 && arr.length) { if (i > 0 && arr.length) {
if (joinIDs.length > 0) { if (joinIDs.length > 0) {
joined.push(joinIDs[0]) joined.push(joinIDs[0]);
joinIDs.splice(0, 1) joinIDs.splice(0, 1);
} else { } else {
joined.push({ type: 'separator' }) joined.push({ type: 'separator' });
} }
} }
return joined.concat(arr) return joined.concat(arr);
}, []) }, []);
} }
function pushOntoMultiMap (map, key, value) { function pushOntoMultiMap (map, key, value) {
if (!map.has(key)) { if (!map.has(key)) {
map.set(key, []) map.set(key, []);
} }
map.get(key).push(value) map.get(key).push(value);
} }
function indexOfGroupContainingID (groups, id, ignoreGroup) { function indexOfGroupContainingID (groups, id, ignoreGroup) {
@@ -45,102 +45,102 @@ function indexOfGroupContainingID (groups, id, ignoreGroup) {
candidateGroup.some( candidateGroup.some(
candidateItem => candidateItem.id === id candidateItem => candidateItem.id === id
) )
) );
} }
// Sort nodes topologically using a depth-first approach. Encountered cycles // Sort nodes topologically using a depth-first approach. Encountered cycles
// are broken. // are broken.
function sortTopologically (originalOrder, edgesById) { function sortTopologically (originalOrder, edgesById) {
const sorted = [] const sorted = [];
const marked = new Set() const marked = new Set();
const visit = (mark) => { const visit = (mark) => {
if (marked.has(mark)) return if (marked.has(mark)) return;
marked.add(mark) marked.add(mark);
const edges = edgesById.get(mark) const edges = edgesById.get(mark);
if (edges != null) { if (edges != null) {
edges.forEach(visit) edges.forEach(visit);
}
sorted.push(mark)
} }
sorted.push(mark);
};
originalOrder.forEach(visit) originalOrder.forEach(visit);
return sorted return sorted;
} }
function attemptToMergeAGroup (groups) { function attemptToMergeAGroup (groups) {
for (let i = 0; i < groups.length; i++) { for (let i = 0; i < groups.length; i++) {
const group = groups[i] const group = groups[i];
for (const item of group) { for (const item of group) {
const toIDs = [...(item.before || []), ...(item.after || [])] const toIDs = [...(item.before || []), ...(item.after || [])];
for (const id of toIDs) { for (const id of toIDs) {
const index = indexOfGroupContainingID(groups, id, group) const index = indexOfGroupContainingID(groups, id, group);
if (index === -1) continue if (index === -1) continue;
const mergeTarget = groups[index] const mergeTarget = groups[index];
mergeTarget.push(...group) mergeTarget.push(...group);
groups.splice(i, 1) groups.splice(i, 1);
return true return true;
} }
} }
} }
return false return false;
} }
function mergeGroups (groups) { function mergeGroups (groups) {
let merged = true let merged = true;
while (merged) { while (merged) {
merged = attemptToMergeAGroup(groups) merged = attemptToMergeAGroup(groups);
} }
return groups return groups;
} }
function sortItemsInGroup (group) { function sortItemsInGroup (group) {
const originalOrder = group.map((node, i) => i) const originalOrder = group.map((node, i) => i);
const edges = new Map() const edges = new Map();
const idToIndex = new Map(group.map((item, i) => [item.id, i])) const idToIndex = new Map(group.map((item, i) => [item.id, i]));
group.forEach((item, i) => { group.forEach((item, i) => {
if (item.before) { if (item.before) {
item.before.forEach(toID => { item.before.forEach(toID => {
const to = idToIndex.get(toID) const to = idToIndex.get(toID);
if (to != null) { if (to != null) {
pushOntoMultiMap(edges, to, i) pushOntoMultiMap(edges, to, i);
} }
}) });
} }
if (item.after) { if (item.after) {
item.after.forEach(toID => { item.after.forEach(toID => {
const to = idToIndex.get(toID) const to = idToIndex.get(toID);
if (to != null) { if (to != null) {
pushOntoMultiMap(edges, i, to) pushOntoMultiMap(edges, i, to);
} }
}) });
} }
}) });
const sortedNodes = sortTopologically(originalOrder, edges) const sortedNodes = sortTopologically(originalOrder, edges);
return sortedNodes.map(i => group[i]) return sortedNodes.map(i => group[i]);
} }
function findEdgesInGroup (groups, i, edges) { function findEdgesInGroup (groups, i, edges) {
const group = groups[i] const group = groups[i];
for (const item of group) { for (const item of group) {
if (item.beforeGroupContaining) { if (item.beforeGroupContaining) {
for (const id of item.beforeGroupContaining) { for (const id of item.beforeGroupContaining) {
const to = indexOfGroupContainingID(groups, id, group) const to = indexOfGroupContainingID(groups, id, group);
if (to !== -1) { if (to !== -1) {
pushOntoMultiMap(edges, to, i) pushOntoMultiMap(edges, to, i);
return return;
} }
} }
} }
if (item.afterGroupContaining) { if (item.afterGroupContaining) {
for (const id of item.afterGroupContaining) { for (const id of item.afterGroupContaining) {
const to = indexOfGroupContainingID(groups, id, group) const to = indexOfGroupContainingID(groups, id, group);
if (to !== -1) { if (to !== -1) {
pushOntoMultiMap(edges, i, to) pushOntoMultiMap(edges, i, to);
return return;
} }
} }
} }
@@ -148,29 +148,29 @@ function findEdgesInGroup (groups, i, edges) {
} }
function sortGroups (groups) { function sortGroups (groups) {
const originalOrder = groups.map((item, i) => i) const originalOrder = groups.map((item, i) => i);
const edges = new Map() const edges = new Map();
for (let i = 0; i < groups.length; i++) { for (let i = 0; i < groups.length; i++) {
findEdgesInGroup(groups, i, edges) findEdgesInGroup(groups, i, edges);
} }
const sortedGroupIndexes = sortTopologically(originalOrder, edges) const sortedGroupIndexes = sortTopologically(originalOrder, edges);
return sortedGroupIndexes.map(i => groups[i]) return sortedGroupIndexes.map(i => groups[i]);
} }
function sortMenuItems (menuItems) { function sortMenuItems (menuItems) {
const isSeparator = (item) => item.type === 'separator' const isSeparator = (item) => item.type === 'separator';
const separators = menuItems.filter(i => i.type === 'separator') const separators = menuItems.filter(i => i.type === 'separator');
// Split the items into their implicit groups based upon separators. // Split the items into their implicit groups based upon separators.
const groups = splitArray(menuItems, isSeparator) const groups = splitArray(menuItems, isSeparator);
const mergedGroups = mergeGroups(groups) const mergedGroups = mergeGroups(groups);
const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup) const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup);
const sortedGroups = sortGroups(mergedGroupsWithSortedItems) const sortedGroups = sortGroups(mergedGroupsWithSortedItems);
const joined = joinArrays(sortedGroups, separators) const joined = joinArrays(sortedGroups, separators);
return joined return joined;
} }
module.exports = { sortMenuItems } module.exports = { sortMenuItems };

View File

@@ -1,16 +1,16 @@
'use strict' 'use strict';
const { TopLevelWindow, MenuItem, webContents } = require('electron') const { TopLevelWindow, MenuItem, webContents } = require('electron');
const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils') const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils');
const EventEmitter = require('events').EventEmitter const EventEmitter = require('events').EventEmitter;
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const bindings = process.electronBinding('menu') const bindings = process.electronBinding('menu');
const { Menu } = bindings const { Menu } = bindings;
let applicationMenu = null let applicationMenu = null;
let groupIdIndex = 0 let groupIdIndex = 0;
Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype) Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype);
// Menu Delegate. // Menu Delegate.
// This object should hold no reference to |Menu| to avoid cyclic reference. // This object should hold no reference to |Menu| to avoid cyclic reference.
@@ -20,172 +20,172 @@ const delegate = {
shouldCommandIdWorkWhenHidden: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].acceleratorWorksWhenHidden : undefined, shouldCommandIdWorkWhenHidden: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].acceleratorWorksWhenHidden : undefined,
isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined, isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined,
getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => { getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => {
const command = menu.commandsMap[id] const command = menu.commandsMap[id];
if (!command) return if (!command) return;
if (command.accelerator != null) return command.accelerator if (command.accelerator != null) return command.accelerator;
if (useDefaultAccelerator) return command.getDefaultRoleAccelerator() if (useDefaultAccelerator) return command.getDefaultRoleAccelerator();
}, },
shouldRegisterAcceleratorForCommandId: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].registerAccelerator : undefined, shouldRegisterAcceleratorForCommandId: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].registerAccelerator : undefined,
executeCommand: (menu, event, id) => { executeCommand: (menu, event, id) => {
const command = menu.commandsMap[id] const command = menu.commandsMap[id];
if (!command) return if (!command) return;
command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents()) command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents());
}, },
menuWillShow: (menu) => { menuWillShow: (menu) => {
// Ensure radio groups have at least one menu item selected // Ensure radio groups have at least one menu item selected
for (const id in menu.groupsMap) { for (const id of Object.keys(menu.groupsMap)) {
const found = menu.groupsMap[id].find(item => item.checked) || null const found = menu.groupsMap[id].find(item => item.checked) || null;
if (!found) v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true) if (!found) v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true);
}
} }
} }
};
/* Instance Methods */ /* Instance Methods */
Menu.prototype._init = function () { Menu.prototype._init = function () {
this.commandsMap = {} this.commandsMap = {};
this.groupsMap = {} this.groupsMap = {};
this.items = [] this.items = [];
this.delegate = delegate this.delegate = delegate;
} };
Menu.prototype.popup = function (options = {}) { Menu.prototype.popup = function (options = {}) {
if (options == null || typeof options !== 'object') { if (options == null || typeof options !== 'object') {
throw new TypeError('Options must be an object') throw new TypeError('Options must be an object');
} }
let { window, x, y, positioningItem, callback } = options let { window, x, y, positioningItem, callback } = options;
// no callback passed // no callback passed
if (!callback || typeof callback !== 'function') callback = () => {} if (!callback || typeof callback !== 'function') callback = () => {};
// set defaults // set defaults
if (typeof x !== 'number') x = -1 if (typeof x !== 'number') x = -1;
if (typeof y !== 'number') y = -1 if (typeof y !== 'number') y = -1;
if (typeof positioningItem !== 'number') positioningItem = -1 if (typeof positioningItem !== 'number') positioningItem = -1;
// find which window to use // find which window to use
const wins = TopLevelWindow.getAllWindows() const wins = TopLevelWindow.getAllWindows();
if (!wins || wins.indexOf(window) === -1) { if (!wins || wins.indexOf(window) === -1) {
window = TopLevelWindow.getFocusedWindow() window = TopLevelWindow.getFocusedWindow();
if (!window && wins && wins.length > 0) { if (!window && wins && wins.length > 0) {
window = wins[0] window = wins[0];
} }
if (!window) { if (!window) {
throw new Error(`Cannot open Menu without a TopLevelWindow present`) throw new Error(`Cannot open Menu without a TopLevelWindow present`);
} }
} }
this.popupAt(window, x, y, positioningItem, callback) this.popupAt(window, x, y, positioningItem, callback);
return { browserWindow: window, x, y, position: positioningItem } return { browserWindow: window, x, y, position: positioningItem };
} };
Menu.prototype.closePopup = function (window) { Menu.prototype.closePopup = function (window) {
if (window instanceof TopLevelWindow) { if (window instanceof TopLevelWindow) {
this.closePopupAt(window.id) this.closePopupAt(window.id);
} else { } else {
// Passing -1 (invalid) would make closePopupAt close the all menu runners // Passing -1 (invalid) would make closePopupAt close the all menu runners
// belong to this menu. // belong to this menu.
this.closePopupAt(-1) this.closePopupAt(-1);
}
} }
};
Menu.prototype.getMenuItemById = function (id) { Menu.prototype.getMenuItemById = function (id) {
const items = this.items const items = this.items;
let found = items.find(item => item.id === id) || null let found = items.find(item => item.id === id) || null;
for (let i = 0; !found && i < items.length; i++) { for (let i = 0; !found && i < items.length; i++) {
if (items[i].submenu) { if (items[i].submenu) {
found = items[i].submenu.getMenuItemById(id) found = items[i].submenu.getMenuItemById(id);
} }
} }
return found return found;
} };
Menu.prototype.append = function (item) { Menu.prototype.append = function (item) {
return this.insert(this.getItemCount(), item) return this.insert(this.getItemCount(), item);
} };
Menu.prototype.insert = function (pos, item) { Menu.prototype.insert = function (pos, item) {
if ((item ? item.constructor : void 0) !== MenuItem) { if ((item ? item.constructor : void 0) !== MenuItem) {
throw new TypeError('Invalid item') throw new TypeError('Invalid item');
} }
if (pos < 0) { if (pos < 0) {
throw new RangeError(`Position ${pos} cannot be less than 0`) throw new RangeError(`Position ${pos} cannot be less than 0`);
} else if (pos > this.getItemCount()) { } else if (pos > this.getItemCount()) {
throw new RangeError(`Position ${pos} cannot be greater than the total MenuItem count`) throw new RangeError(`Position ${pos} cannot be greater than the total MenuItem count`);
} }
// insert item depending on its type // insert item depending on its type
insertItemByType.call(this, item, pos) insertItemByType.call(this, item, pos);
// set item properties // set item properties
if (item.sublabel) this.setSublabel(pos, item.sublabel) if (item.sublabel) this.setSublabel(pos, item.sublabel);
if (item.toolTip) this.setToolTip(pos, item.toolTip) if (item.toolTip) this.setToolTip(pos, item.toolTip);
if (item.icon) this.setIcon(pos, item.icon) if (item.icon) this.setIcon(pos, item.icon);
if (item.role) this.setRole(pos, item.role) if (item.role) this.setRole(pos, item.role);
// Make menu accessable to items. // Make menu accessable to items.
item.overrideReadOnlyProperty('menu', this) item.overrideReadOnlyProperty('menu', this);
// Remember the items. // Remember the items.
this.items.splice(pos, 0, item) this.items.splice(pos, 0, item);
this.commandsMap[item.commandId] = item this.commandsMap[item.commandId] = item;
} };
Menu.prototype._callMenuWillShow = function () { Menu.prototype._callMenuWillShow = function () {
if (this.delegate) this.delegate.menuWillShow(this) if (this.delegate) this.delegate.menuWillShow(this);
this.items.forEach(item => { this.items.forEach(item => {
if (item.submenu) item.submenu._callMenuWillShow() if (item.submenu) item.submenu._callMenuWillShow();
}) });
} };
/* Static Methods */ /* Static Methods */
Menu.getApplicationMenu = () => applicationMenu Menu.getApplicationMenu = () => applicationMenu;
Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder;
// set application menu with a preexisting menu // set application menu with a preexisting menu
Menu.setApplicationMenu = function (menu) { Menu.setApplicationMenu = function (menu) {
if (menu && menu.constructor !== Menu) { if (menu && menu.constructor !== Menu) {
throw new TypeError('Invalid menu') throw new TypeError('Invalid menu');
} }
applicationMenu = menu applicationMenu = menu;
v8Util.setHiddenValue(global, 'applicationMenuSet', true) v8Util.setHiddenValue(global, 'applicationMenuSet', true);
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
if (!menu) return if (!menu) return;
menu._callMenuWillShow() menu._callMenuWillShow();
bindings.setApplicationMenu(menu) bindings.setApplicationMenu(menu);
} else { } else {
const windows = TopLevelWindow.getAllWindows() const windows = TopLevelWindow.getAllWindows();
return windows.map(w => w.setMenu(menu)) return windows.map(w => w.setMenu(menu));
}
} }
};
Menu.buildFromTemplate = function (template) { Menu.buildFromTemplate = function (template) {
if (!Array.isArray(template)) { if (!Array.isArray(template)) {
throw new TypeError('Invalid template for Menu: Menu template must be an array') throw new TypeError('Invalid template for Menu: Menu template must be an array');
} }
if (!areValidTemplateItems(template)) { if (!areValidTemplateItems(template)) {
throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type') throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type');
} }
const filtered = removeExtraSeparators(template) const filtered = removeExtraSeparators(template);
const sorted = sortTemplate(filtered) const sorted = sortTemplate(filtered);
const menu = new Menu() const menu = new Menu();
sorted.forEach(item => { sorted.forEach(item => {
if (item instanceof MenuItem) { if (item instanceof MenuItem) {
menu.append(item) menu.append(item);
} else { } else {
menu.append(new MenuItem(item)) menu.append(new MenuItem(item));
} }
}) });
return menu return menu;
} };
/* Helper Functions */ /* Helper Functions */
@@ -196,51 +196,50 @@ function areValidTemplateItems (template) {
typeof item === 'object' && typeof item === 'object' &&
(item.hasOwnProperty('label') || (item.hasOwnProperty('label') ||
item.hasOwnProperty('role') || item.hasOwnProperty('role') ||
item.type === 'separator')) item.type === 'separator'));
} }
function sortTemplate (template) { function sortTemplate (template) {
const sorted = sortMenuItems(template) const sorted = sortMenuItems(template);
for (const id in sorted) { for (const item of sorted) {
const item = sorted[id]
if (Array.isArray(item.submenu)) { if (Array.isArray(item.submenu)) {
item.submenu = sortTemplate(item.submenu) item.submenu = sortTemplate(item.submenu);
} }
} }
return sorted return sorted;
} }
// Search between separators to find a radio menu item and return its group id // Search between separators to find a radio menu item and return its group id
function generateGroupId (items, pos) { function generateGroupId (items, pos) {
if (pos > 0) { if (pos > 0) {
for (let idx = pos - 1; idx >= 0; idx--) { for (let idx = pos - 1; idx >= 0; idx--) {
if (items[idx].type === 'radio') return items[idx].groupId if (items[idx].type === 'radio') return items[idx].groupId;
if (items[idx].type === 'separator') break if (items[idx].type === 'separator') break;
} }
} else if (pos < items.length) { } else if (pos < items.length) {
for (let idx = pos; idx <= items.length - 1; idx++) { for (let idx = pos; idx <= items.length - 1; idx++) {
if (items[idx].type === 'radio') return items[idx].groupId if (items[idx].type === 'radio') return items[idx].groupId;
if (items[idx].type === 'separator') break if (items[idx].type === 'separator') break;
} }
} }
groupIdIndex += 1 groupIdIndex += 1;
return groupIdIndex return groupIdIndex;
} }
function removeExtraSeparators (items) { function removeExtraSeparators (items) {
// fold adjacent separators together // fold adjacent separators together
let ret = items.filter((e, idx, arr) => { let ret = items.filter((e, idx, arr) => {
if (e.visible === false) return true if (e.visible === false) return true;
return e.type !== 'separator' || idx === 0 || arr[idx - 1].type !== 'separator' return e.type !== 'separator' || idx === 0 || arr[idx - 1].type !== 'separator';
}) });
// remove edge separators // remove edge separators
ret = ret.filter((e, idx, arr) => { ret = ret.filter((e, idx, arr) => {
if (e.visible === false) return true if (e.visible === false) return true;
return e.type !== 'separator' || (idx !== 0 && idx !== arr.length - 1) return e.type !== 'separator' || (idx !== 0 && idx !== arr.length - 1);
}) });
return ret return ret;
} }
function insertItemByType (item, pos) { function insertItemByType (item, pos) {
@@ -251,28 +250,28 @@ function insertItemByType (item, pos) {
submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu), submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
radio: () => { radio: () => {
// Grouping radio menu items // Grouping radio menu items
item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos)) item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos));
if (this.groupsMap[item.groupId] == null) { if (this.groupsMap[item.groupId] == null) {
this.groupsMap[item.groupId] = [] this.groupsMap[item.groupId] = [];
} }
this.groupsMap[item.groupId].push(item) this.groupsMap[item.groupId].push(item);
// Setting a radio menu item should flip other items in the group. // Setting a radio menu item should flip other items in the group.
v8Util.setHiddenValue(item, 'checked', item.checked) v8Util.setHiddenValue(item, 'checked', item.checked);
Object.defineProperty(item, 'checked', { Object.defineProperty(item, 'checked', {
enumerable: true, enumerable: true,
get: () => v8Util.getHiddenValue(item, 'checked'), get: () => v8Util.getHiddenValue(item, 'checked'),
set: () => { set: () => {
this.groupsMap[item.groupId].forEach(other => { this.groupsMap[item.groupId].forEach(other => {
if (other !== item) v8Util.setHiddenValue(other, 'checked', false) if (other !== item) v8Util.setHiddenValue(other, 'checked', false);
}) });
v8Util.setHiddenValue(item, 'checked', true) v8Util.setHiddenValue(item, 'checked', true);
} }
}) });
this.insertRadioItem(pos, item.commandId, item.label, item.groupId) this.insertRadioItem(pos, item.commandId, item.label, item.groupId);
} }
} };
types[item.type]() types[item.type]();
} }
module.exports = Menu module.exports = Menu;

View File

@@ -1,11 +1,11 @@
'use strict' 'use strict';
// TODO: Figure out a way to not duplicate this information between here and module-list // TODO: Figure out a way to not duplicate this information between here and module-list
// It is currently duplicated as module-list "require"s all the browser API file and the // It is currently duplicated as module-list "require"s all the browser API file and the
// remote module in the renderer process depends on that file. As a result webpack // remote module in the renderer process depends on that file. As a result webpack
// includes all the browser API files in the renderer process as well and we want to avoid that // includes all the browser API files in the renderer process as well and we want to avoid that
const features = process.electronBinding('features') const features = process.electronBinding('features');
// Browser side modules, please sort alphabetically. // Browser side modules, please sort alphabetically.
module.exports = [ module.exports = [
@@ -37,7 +37,7 @@ module.exports = [
{ name: 'View' }, { name: 'View' },
{ name: 'webContents' }, { name: 'webContents' },
{ name: 'WebContentsView' } { name: 'WebContentsView' }
] ];
if (features.isViewApiEnabled()) { if (features.isViewApiEnabled()) {
module.exports.push( module.exports.push(
@@ -48,5 +48,5 @@ if (features.isViewApiEnabled()) {
{ name: 'MdTextButton' }, { name: 'MdTextButton' },
{ name: 'ResizeArea' }, { name: 'ResizeArea' },
{ name: 'TextField' } { name: 'TextField' }
) );
} }

View File

@@ -1,6 +1,6 @@
// TODO: Updating this file also required updating the module-keys file // TODO: Updating this file also required updating the module-keys file
const features = process.electronBinding('features') const features = process.electronBinding('features');
// Browser side modules, please sort alphabetically. // Browser side modules, please sort alphabetically.
export const browserModuleList: ElectronInternal.ModuleEntry[] = [ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
@@ -32,7 +32,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'View', loader: () => require('./view') }, { name: 'View', loader: () => require('./view') },
{ name: 'webContents', loader: () => require('./web-contents') }, { name: 'webContents', loader: () => require('./web-contents') },
{ name: 'WebContentsView', loader: () => require('./web-contents-view') } { name: 'WebContentsView', loader: () => require('./web-contents-view') }
] ];
if (features.isViewApiEnabled()) { if (features.isViewApiEnabled()) {
browserModuleList.push( browserModuleList.push(
@@ -43,5 +43,5 @@ if (features.isViewApiEnabled()) {
{ name: 'MdTextButton', loader: () => require('./views/md-text-button') }, { name: 'MdTextButton', loader: () => require('./views/md-text-button') },
{ name: 'ResizeArea', loader: () => require('./views/resize-area') }, { name: 'ResizeArea', loader: () => require('./views/resize-area') },
{ name: 'TextField', loader: () => require('./views/text-field') } { name: 'TextField', loader: () => require('./views/text-field') }
) );
} }

View File

@@ -1,8 +1,8 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
const { NativeTheme, nativeTheme } = process.electronBinding('native_theme') const { NativeTheme, nativeTheme } = process.electronBinding('native_theme');
Object.setPrototypeOf(NativeTheme.prototype, EventEmitter.prototype) Object.setPrototypeOf(NativeTheme.prototype, EventEmitter.prototype);
EventEmitter.call(nativeTheme as any) EventEmitter.call(nativeTheme as any);
module.exports = nativeTheme module.exports = nativeTheme;

View File

@@ -1,32 +1,32 @@
'use strict' 'use strict';
// TODO(deepak1556): Deprecate and remove standalone netLog module, // TODO(deepak1556): Deprecate and remove standalone netLog module,
// it is now a property of session module. // it is now a property of session module.
const { app, session } = require('electron') const { app, session } = require('electron');
// Fallback to default session. // Fallback to default session.
Object.setPrototypeOf(module.exports, new Proxy({}, { Object.setPrototypeOf(module.exports, new Proxy({}, {
get (target, property) { get (target, property) {
if (!app.isReady()) return if (!app.isReady()) return;
const netLog = session.defaultSession.netLog const netLog = session.defaultSession.netLog;
if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) return if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) return;
// check for properties on the prototype chain that aren't functions // check for properties on the prototype chain that aren't functions
if (typeof netLog[property] !== 'function') return netLog[property] if (typeof netLog[property] !== 'function') return netLog[property];
// Returning a native function directly would throw error. // Returning a native function directly would throw error.
return (...args) => netLog[property](...args) return (...args) => netLog[property](...args);
}, },
ownKeys () { ownKeys () {
if (!app.isReady()) return [] if (!app.isReady()) return [];
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog)) return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog));
}, },
getOwnPropertyDescriptor (target) { getOwnPropertyDescriptor (target) {
return { configurable: true, enumerable: true } return { configurable: true, enumerable: true };
} }
})) }));

View File

@@ -1,16 +1,16 @@
'use strict' 'use strict';
const url = require('url') const url = require('url');
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { Readable, Writable } = require('stream') const { Readable, Writable } = require('stream');
const { app } = require('electron') const { app } = require('electron');
const { Session } = process.electronBinding('session') const { Session } = process.electronBinding('session');
const { net, Net, _isValidHeaderName, _isValidHeaderValue } = process.electronBinding('net') const { net, Net, _isValidHeaderName, _isValidHeaderValue } = process.electronBinding('net');
const { URLLoader } = net const { URLLoader } = net;
Object.setPrototypeOf(URLLoader.prototype, EventEmitter.prototype) Object.setPrototypeOf(URLLoader.prototype, EventEmitter.prototype);
const kSupportedProtocols = new Set(['http:', 'https:']) const kSupportedProtocols = new Set(['http:', 'https:']);
// set of headers that Node.js discards duplicates for // set of headers that Node.js discards duplicates for
// see https://nodejs.org/api/http.html#http_message_headers // see https://nodejs.org/api/http.html#http_message_headers
@@ -33,27 +33,27 @@ const discardableDuplicateHeaders = new Set([
'server', 'server',
'age', 'age',
'expires' 'expires'
]) ]);
class IncomingMessage extends Readable { class IncomingMessage extends Readable {
constructor (responseHead) { constructor (responseHead) {
super() super();
this._shouldPush = false this._shouldPush = false;
this._data = [] this._data = [];
this._responseHead = responseHead this._responseHead = responseHead;
} }
get statusCode () { get statusCode () {
return this._responseHead.statusCode return this._responseHead.statusCode;
} }
get statusMessage () { get statusMessage () {
return this._responseHead.statusMessage return this._responseHead.statusMessage;
} }
get headers () { get headers () {
const filteredHeaders = {} const filteredHeaders = {};
const { rawHeaders } = this._responseHead const { rawHeaders } = this._responseHead;
rawHeaders.forEach(header => { rawHeaders.forEach(header => {
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key) && if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key) &&
discardableDuplicateHeaders.has(header.key)) { discardableDuplicateHeaders.has(header.key)) {
@@ -63,145 +63,145 @@ class IncomingMessage extends Readable {
// keep set-cookie as an array per Node.js rules // keep set-cookie as an array per Node.js rules
// see https://nodejs.org/api/http.html#http_message_headers // see https://nodejs.org/api/http.html#http_message_headers
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) { if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
filteredHeaders[header.key].push(header.value) filteredHeaders[header.key].push(header.value);
} else { } else {
filteredHeaders[header.key] = [header.value] filteredHeaders[header.key] = [header.value];
} }
} else { } else {
// for non-cookie headers, the values are joined together with ', ' // for non-cookie headers, the values are joined together with ', '
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) { if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
filteredHeaders[header.key] += `, ${header.value}` filteredHeaders[header.key] += `, ${header.value}`;
} else { } else {
filteredHeaders[header.key] = header.value filteredHeaders[header.key] = header.value;
} }
} }
} }
}) });
return filteredHeaders return filteredHeaders;
} }
get httpVersion () { get httpVersion () {
return `${this.httpVersionMajor}.${this.httpVersionMinor}` return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
} }
get httpVersionMajor () { get httpVersionMajor () {
return this._responseHead.httpVersion.major return this._responseHead.httpVersion.major;
} }
get httpVersionMinor () { get httpVersionMinor () {
return this._responseHead.httpVersion.minor return this._responseHead.httpVersion.minor;
} }
get rawTrailers () { get rawTrailers () {
throw new Error('HTTP trailers are not supported') throw new Error('HTTP trailers are not supported');
} }
get trailers () { get trailers () {
throw new Error('HTTP trailers are not supported') throw new Error('HTTP trailers are not supported');
} }
_storeInternalData (chunk) { _storeInternalData (chunk) {
this._data.push(chunk) this._data.push(chunk);
this._pushInternalData() this._pushInternalData();
} }
_pushInternalData () { _pushInternalData () {
while (this._shouldPush && this._data.length > 0) { while (this._shouldPush && this._data.length > 0) {
const chunk = this._data.shift() const chunk = this._data.shift();
this._shouldPush = this.push(chunk) this._shouldPush = this.push(chunk);
} }
} }
_read () { _read () {
this._shouldPush = true this._shouldPush = true;
this._pushInternalData() this._pushInternalData();
} }
} }
/** Writable stream that buffers up everything written to it. */ /** Writable stream that buffers up everything written to it. */
class SlurpStream extends Writable { class SlurpStream extends Writable {
constructor () { constructor () {
super() super();
this._data = Buffer.alloc(0) this._data = Buffer.alloc(0);
} }
_write (chunk, encoding, callback) { _write (chunk, encoding, callback) {
this._data = Buffer.concat([this._data, chunk]) this._data = Buffer.concat([this._data, chunk]);
callback() callback();
} }
data () { return this._data } data () { return this._data; }
} }
class ChunkedBodyStream extends Writable { class ChunkedBodyStream extends Writable {
constructor (clientRequest) { constructor (clientRequest) {
super() super();
this._clientRequest = clientRequest this._clientRequest = clientRequest;
} }
_write (chunk, encoding, callback) { _write (chunk, encoding, callback) {
if (this._downstream) { if (this._downstream) {
this._downstream.write(chunk).then(callback, callback) this._downstream.write(chunk).then(callback, callback);
} else { } else {
// the contract of _write is that we won't be called again until we call // the contract of _write is that we won't be called again until we call
// the callback, so we're good to just save a single chunk. // the callback, so we're good to just save a single chunk.
this._pendingChunk = chunk this._pendingChunk = chunk;
this._pendingCallback = callback this._pendingCallback = callback;
// The first write to a chunked body stream begins the request. // The first write to a chunked body stream begins the request.
this._clientRequest._startRequest() this._clientRequest._startRequest();
} }
} }
_final (callback) { _final (callback) {
this._downstream.done() this._downstream.done();
callback() callback();
} }
startReading (pipe) { startReading (pipe) {
if (this._downstream) { if (this._downstream) {
throw new Error('two startReading calls???') throw new Error('two startReading calls???');
} }
this._downstream = pipe this._downstream = pipe;
if (this._pendingChunk) { if (this._pendingChunk) {
const doneWriting = (maybeError) => { const doneWriting = (maybeError) => {
const cb = this._pendingCallback const cb = this._pendingCallback;
delete this._pendingCallback delete this._pendingCallback;
delete this._pendingChunk delete this._pendingChunk;
cb(maybeError) cb(maybeError);
} };
this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting) this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting);
} }
} }
} }
function parseOptions (options) { function parseOptions (options) {
if (typeof options === 'string') { if (typeof options === 'string') {
options = url.parse(options) options = url.parse(options);
} else { } else {
options = { ...options } options = { ...options };
} }
const method = (options.method || 'GET').toUpperCase() const method = (options.method || 'GET').toUpperCase();
let urlStr = options.url let urlStr = options.url;
if (!urlStr) { if (!urlStr) {
const urlObj = {} const urlObj = {};
const protocol = options.protocol || 'http:' const protocol = options.protocol || 'http:';
if (!kSupportedProtocols.has(protocol)) { if (!kSupportedProtocols.has(protocol)) {
throw new Error('Protocol "' + protocol + '" not supported') throw new Error('Protocol "' + protocol + '" not supported');
} }
urlObj.protocol = protocol urlObj.protocol = protocol;
if (options.host) { if (options.host) {
urlObj.host = options.host urlObj.host = options.host;
} else { } else {
if (options.hostname) { if (options.hostname) {
urlObj.hostname = options.hostname urlObj.hostname = options.hostname;
} else { } else {
urlObj.hostname = 'localhost' urlObj.hostname = 'localhost';
} }
if (options.port) { if (options.port) {
urlObj.port = options.port urlObj.port = options.port;
} }
} }
@@ -212,203 +212,204 @@ function parseOptions (options) {
// well, and b) possibly too restrictive for real-world usage. That's // well, and b) possibly too restrictive for real-world usage. That's
// why it only scans for spaces because those are guaranteed to create // why it only scans for spaces because those are guaranteed to create
// an invalid request. // an invalid request.
throw new TypeError('Request path contains unescaped characters') throw new TypeError('Request path contains unescaped characters');
} }
const pathObj = url.parse(options.path || '/') const pathObj = url.parse(options.path || '/');
urlObj.pathname = pathObj.pathname urlObj.pathname = pathObj.pathname;
urlObj.search = pathObj.search urlObj.search = pathObj.search;
urlObj.hash = pathObj.hash urlObj.hash = pathObj.hash;
urlStr = url.format(urlObj) urlStr = url.format(urlObj);
} }
const redirectPolicy = options.redirect || 'follow' const redirectPolicy = options.redirect || 'follow';
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) { if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
throw new Error('redirect mode should be one of follow, error or manual') throw new Error('redirect mode should be one of follow, error or manual');
} }
if (options.headers != null && typeof options.headers !== 'object') { if (options.headers != null && typeof options.headers !== 'object') {
throw new TypeError('headers must be an object') throw new TypeError('headers must be an object');
} }
const urlLoaderOptions = { const urlLoaderOptions = {
method: method, method: method,
url: urlStr, url: urlStr,
redirectPolicy, redirectPolicy,
extraHeaders: options.headers || {} extraHeaders: options.headers || {},
} useSessionCookies: options.useSessionCookies || false
};
for (const [name, value] of Object.entries(urlLoaderOptions.extraHeaders)) { for (const [name, value] of Object.entries(urlLoaderOptions.extraHeaders)) {
if (!_isValidHeaderName(name)) { if (!_isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`) throw new Error(`Invalid header name: '${name}'`);
} }
if (!_isValidHeaderValue(value.toString())) { if (!_isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`) throw new Error(`Invalid value for header '${name}': '${value}'`);
} }
} }
if (options.session) { if (options.session) {
if (options.session instanceof Session) { if (options.session instanceof Session) {
urlLoaderOptions.session = options.session urlLoaderOptions.session = options.session;
} else { } else {
throw new TypeError('`session` should be an instance of the Session class') throw new TypeError('`session` should be an instance of the Session class');
} }
} else if (options.partition) { } else if (options.partition) {
if (typeof options.partition === 'string') { if (typeof options.partition === 'string') {
urlLoaderOptions.partition = options.partition urlLoaderOptions.partition = options.partition;
} else { } else {
throw new TypeError('`partition` should be a string') throw new TypeError('`partition` should be a string');
} }
} }
return urlLoaderOptions return urlLoaderOptions;
} }
class ClientRequest extends Writable { class ClientRequest extends Writable {
constructor (options, callback) { constructor (options, callback) {
super({ autoDestroy: true }) super({ autoDestroy: true });
if (!app.isReady()) { if (!app.isReady()) {
throw new Error('net module can only be used after app is ready') throw new Error('net module can only be used after app is ready');
} }
if (callback) { if (callback) {
this.once('response', callback) this.once('response', callback);
} }
const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options) const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options);
this._urlLoaderOptions = urlLoaderOptions this._urlLoaderOptions = urlLoaderOptions;
this._redirectPolicy = redirectPolicy this._redirectPolicy = redirectPolicy;
this._started = false this._started = false;
} }
set chunkedEncoding (value) { set chunkedEncoding (value) {
if (this._started) { if (this._started) {
throw new Error('chunkedEncoding can only be set before the request is started') throw new Error('chunkedEncoding can only be set before the request is started');
} }
if (typeof this._chunkedEncoding !== 'undefined') { if (typeof this._chunkedEncoding !== 'undefined') {
throw new Error('chunkedEncoding can only be set once') throw new Error('chunkedEncoding can only be set once');
} }
this._chunkedEncoding = !!value this._chunkedEncoding = !!value;
if (this._chunkedEncoding) { if (this._chunkedEncoding) {
this._body = new ChunkedBodyStream(this) this._body = new ChunkedBodyStream(this);
this._urlLoaderOptions.body = (pipe) => { this._urlLoaderOptions.body = (pipe) => {
this._body.startReading(pipe) this._body.startReading(pipe);
} };
} }
} }
setHeader (name, value) { setHeader (name, value) {
if (typeof name !== 'string') { if (typeof name !== 'string') {
throw new TypeError('`name` should be a string in setHeader(name, value)') throw new TypeError('`name` should be a string in setHeader(name, value)');
} }
if (value == null) { if (value == null) {
throw new Error('`value` required in setHeader("' + name + '", value)') throw new Error('`value` required in setHeader("' + name + '", value)');
} }
if (this._started || this._firstWrite) { if (this._started || this._firstWrite) {
throw new Error('Can\'t set headers after they are sent') throw new Error('Can\'t set headers after they are sent');
} }
if (!_isValidHeaderName(name)) { if (!_isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`) throw new Error(`Invalid header name: '${name}'`);
} }
if (!_isValidHeaderValue(value.toString())) { if (!_isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`) throw new Error(`Invalid value for header '${name}': '${value}'`);
} }
const key = name.toLowerCase() const key = name.toLowerCase();
this._urlLoaderOptions.extraHeaders[key] = value this._urlLoaderOptions.extraHeaders[key] = value;
} }
getHeader (name) { getHeader (name) {
if (name == null) { if (name == null) {
throw new Error('`name` is required for getHeader(name)') throw new Error('`name` is required for getHeader(name)');
} }
const key = name.toLowerCase() const key = name.toLowerCase();
return this._urlLoaderOptions.extraHeaders[key] return this._urlLoaderOptions.extraHeaders[key];
} }
removeHeader (name) { removeHeader (name) {
if (name == null) { if (name == null) {
throw new Error('`name` is required for removeHeader(name)') throw new Error('`name` is required for removeHeader(name)');
} }
if (this._started || this._firstWrite) { if (this._started || this._firstWrite) {
throw new Error('Can\'t remove headers after they are sent') throw new Error('Can\'t remove headers after they are sent');
} }
const key = name.toLowerCase() const key = name.toLowerCase();
delete this._urlLoaderOptions.extraHeaders[key] delete this._urlLoaderOptions.extraHeaders[key];
} }
_write (chunk, encoding, callback) { _write (chunk, encoding, callback) {
this._firstWrite = true this._firstWrite = true;
if (!this._body) { if (!this._body) {
this._body = new SlurpStream() this._body = new SlurpStream();
this._body.on('finish', () => { this._body.on('finish', () => {
this._urlLoaderOptions.body = this._body.data() this._urlLoaderOptions.body = this._body.data();
this._startRequest() this._startRequest();
}) });
} }
// TODO: is this the right way to forward to another stream? // TODO: is this the right way to forward to another stream?
this._body.write(chunk, encoding, callback) this._body.write(chunk, encoding, callback);
} }
_final (callback) { _final (callback) {
if (this._body) { if (this._body) {
// TODO: is this the right way to forward to another stream? // TODO: is this the right way to forward to another stream?
this._body.end(callback) this._body.end(callback);
} else { } else {
// end() called without a body, go ahead and start the request // end() called without a body, go ahead and start the request
this._startRequest() this._startRequest();
callback() callback();
} }
} }
_startRequest () { _startRequest () {
this._started = true this._started = true;
const stringifyValues = (obj) => { const stringifyValues = (obj) => {
const ret = {} const ret = {};
for (const k in obj) { for (const k of Object.keys(obj)) {
ret[k] = obj[k].toString() ret[k] = obj[k].toString();
} }
return ret return ret;
} };
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.extraHeaders) } const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.extraHeaders) };
this._urlLoader = new URLLoader(opts) this._urlLoader = new URLLoader(opts);
this._urlLoader.on('response-started', (event, finalUrl, responseHead) => { this._urlLoader.on('response-started', (event, finalUrl, responseHead) => {
const response = this._response = new IncomingMessage(responseHead) const response = this._response = new IncomingMessage(responseHead);
this.emit('response', response) this.emit('response', response);
}) });
this._urlLoader.on('data', (event, data) => { this._urlLoader.on('data', (event, data) => {
this._response._storeInternalData(Buffer.from(data)) this._response._storeInternalData(Buffer.from(data));
}) });
this._urlLoader.on('complete', () => { this._urlLoader.on('complete', () => {
if (this._response) { this._response._storeInternalData(null) } if (this._response) { this._response._storeInternalData(null); }
}) });
this._urlLoader.on('error', (event, netErrorString) => { this._urlLoader.on('error', (event, netErrorString) => {
const error = new Error(netErrorString) const error = new Error(netErrorString);
if (this._response) this._response.destroy(error) if (this._response) this._response.destroy(error);
this._die(error) this._die(error);
}) });
this._urlLoader.on('login', (event, authInfo, callback) => { this._urlLoader.on('login', (event, authInfo, callback) => {
const handled = this.emit('login', authInfo, callback) const handled = this.emit('login', authInfo, callback);
if (!handled) { if (!handled) {
// If there were no listeners, cancel the authentication request. // If there were no listeners, cancel the authentication request.
callback() callback();
} }
}) });
this._urlLoader.on('redirect', (event, redirectInfo, headers) => { this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
const { statusCode, newMethod, newUrl } = redirectInfo const { statusCode, newMethod, newUrl } = redirectInfo;
if (this._redirectPolicy === 'error') { if (this._redirectPolicy === 'error') {
this._die(new Error(`Attempted to redirect, but redirect policy was 'error'`)) this._die(new Error(`Attempted to redirect, but redirect policy was 'error'`));
} else if (this._redirectPolicy === 'manual') { } else if (this._redirectPolicy === 'manual') {
let _followRedirect = false let _followRedirect = false;
this._followRedirectCb = () => { _followRedirect = true } this._followRedirectCb = () => { _followRedirect = true; };
try { try {
this.emit('redirect', statusCode, newMethod, newUrl, headers) this.emit('redirect', statusCode, newMethod, newUrl, headers);
} finally { } finally {
this._followRedirectCb = null this._followRedirectCb = null;
if (!_followRedirect && !this._aborted) { if (!_followRedirect && !this._aborted) {
this._die(new Error('Redirect was cancelled')) this._die(new Error('Redirect was cancelled'));
} }
} }
} else if (this._redirectPolicy === 'follow') { } else if (this._redirectPolicy === 'follow') {
@@ -416,61 +417,61 @@ class ClientRequest extends Writable {
// allowed but does nothing. (Perhaps it should throw an error // allowed but does nothing. (Perhaps it should throw an error
// though...? Since the redirect will happen regardless.) // though...? Since the redirect will happen regardless.)
try { try {
this._followRedirectCb = () => {} this._followRedirectCb = () => {};
this.emit('redirect', statusCode, newMethod, newUrl, headers) this.emit('redirect', statusCode, newMethod, newUrl, headers);
} finally { } finally {
this._followRedirectCb = null this._followRedirectCb = null;
} }
} else { } else {
this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`)) this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`));
} }
}) });
this._urlLoader.on('upload-progress', (event, position, total) => { this._urlLoader.on('upload-progress', (event, position, total) => {
this._uploadProgress = { active: true, started: true, current: position, total } this._uploadProgress = { active: true, started: true, current: position, total };
this.emit('upload-progress', position, total) // Undocumented, for now this.emit('upload-progress', position, total); // Undocumented, for now
}) });
this._urlLoader.on('download-progress', (event, current) => { this._urlLoader.on('download-progress', (event, current) => {
if (this._response) { if (this._response) {
this._response.emit('download-progress', current) // Undocumented, for now this._response.emit('download-progress', current); // Undocumented, for now
} }
}) });
} }
followRedirect () { followRedirect () {
if (this._followRedirectCb) { if (this._followRedirectCb) {
this._followRedirectCb() this._followRedirectCb();
} else { } else {
throw new Error('followRedirect() called, but was not waiting for a redirect') throw new Error('followRedirect() called, but was not waiting for a redirect');
} }
} }
abort () { abort () {
if (!this._aborted) { if (!this._aborted) {
process.nextTick(() => { this.emit('abort') }) process.nextTick(() => { this.emit('abort'); });
} }
this._aborted = true this._aborted = true;
this._die() this._die();
} }
_die (err) { _die (err) {
this.destroy(err) this.destroy(err);
if (this._urlLoader) { if (this._urlLoader) {
this._urlLoader.cancel() this._urlLoader.cancel();
if (this._response) this._response.destroy(err) if (this._response) this._response.destroy(err);
} }
} }
getUploadProgress () { getUploadProgress () {
return this._uploadProgress ? { ...this._uploadProgress } : { active: false } return this._uploadProgress ? { ...this._uploadProgress } : { active: false };
} }
} }
Net.prototype.request = function (options, callback) { Net.prototype.request = function (options, callback) {
return new ClientRequest(options, callback) return new ClientRequest(options, callback);
} };
net.ClientRequest = ClientRequest net.ClientRequest = ClientRequest;
module.exports = net module.exports = net;

View File

@@ -1,10 +1,10 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { Notification, isSupported } = process.electronBinding('notification') const { Notification, isSupported } = process.electronBinding('notification');
Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype) Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype);
Notification.isSupported = isSupported Notification.isSupported = isSupported;
module.exports = Notification module.exports = Notification;

View File

@@ -1,14 +1,14 @@
'use strict' 'use strict';
import { createLazyInstance } from '../utils' import { createLazyInstance } from '../utils';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { createPowerMonitor, PowerMonitor } = process.electronBinding('power_monitor') const { createPowerMonitor, PowerMonitor } = process.electronBinding('power_monitor');
// PowerMonitor is an EventEmitter. // PowerMonitor is an EventEmitter.
Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype) Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype);
const powerMonitor = createLazyInstance(createPowerMonitor, PowerMonitor, true) const powerMonitor = createLazyInstance(createPowerMonitor, PowerMonitor, true);
if (process.platform === 'linux') { if (process.platform === 'linux') {
// In order to delay system shutdown when e.preventDefault() is invoked // In order to delay system shutdown when e.preventDefault() is invoked
@@ -18,21 +18,21 @@ if (process.platform === 'linux') {
// //
// So here we watch for 'shutdown' listeners to be added or removed and // So here we watch for 'shutdown' listeners to be added or removed and
// set or unset our shutdown delay lock accordingly. // set or unset our shutdown delay lock accordingly.
const { app } = require('electron') const { app } = require('electron');
app.whenReady().then(() => { app.whenReady().then(() => {
powerMonitor.on('newListener', (event: string) => { powerMonitor.on('newListener', (event: string) => {
// whenever the listener count is incremented to one... // whenever the listener count is incremented to one...
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
powerMonitor.blockShutdown() powerMonitor.blockShutdown();
} }
}) });
powerMonitor.on('removeListener', (event: string) => { powerMonitor.on('removeListener', (event: string) => {
// whenever the listener count is decremented to zero... // whenever the listener count is decremented to zero...
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) { if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
powerMonitor.unblockShutdown() powerMonitor.unblockShutdown();
} }
}) });
}) });
} }
module.exports = powerMonitor module.exports = powerMonitor;

View File

@@ -1,3 +1,3 @@
'use strict' 'use strict';
module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker;

View File

@@ -1,29 +1,29 @@
import { app, session } from 'electron' import { app, session } from 'electron';
// Global protocol APIs. // Global protocol APIs.
const protocol = process.electronBinding('protocol') const protocol = process.electronBinding('protocol');
// Fallback protocol APIs of default session. // Fallback protocol APIs of default session.
Object.setPrototypeOf(protocol, new Proxy({}, { Object.setPrototypeOf(protocol, new Proxy({}, {
get (_target, property) { get (_target, property) {
if (!app.isReady()) return if (!app.isReady()) return;
const protocol = session.defaultSession!.protocol const protocol = session.defaultSession!.protocol;
if (!Object.getPrototypeOf(protocol).hasOwnProperty(property)) return if (!Object.getPrototypeOf(protocol).hasOwnProperty(property)) return;
// Returning a native function directly would throw error. // Returning a native function directly would throw error.
return (...args: any[]) => (protocol[property as keyof Electron.Protocol] as Function)(...args) return (...args: any[]) => (protocol[property as keyof Electron.Protocol] as Function)(...args);
}, },
ownKeys () { ownKeys () {
if (!app.isReady()) return [] if (!app.isReady()) return [];
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession!.protocol)) return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession!.protocol));
}, },
getOwnPropertyDescriptor () { getOwnPropertyDescriptor () {
return { configurable: true, enumerable: true } return { configurable: true, enumerable: true };
} }
})) }));
export default protocol export default protocol;

View File

@@ -1,10 +1,10 @@
'use strict' 'use strict';
import { createLazyInstance } from '../utils' import { createLazyInstance } from '../utils';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { Screen, createScreen } = process.electronBinding('screen') const { Screen, createScreen } = process.electronBinding('screen');
// Screen is an EventEmitter. // Screen is an EventEmitter.
Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype) Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype);
module.exports = createLazyInstance(createScreen, Screen, true) module.exports = createLazyInstance(createScreen, Screen, true);

View File

@@ -1,53 +1,54 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { app, deprecate } = require('electron') const { app, deprecate } = require('electron');
const { fromPartition, Session, Cookies, NetLog, Protocol } = process.electronBinding('session') const { fromPartition, Session, Cookies, NetLog, Protocol, ServiceWorkerContext } = process.electronBinding('session');
// Public API. // Public API.
Object.defineProperties(exports, { Object.defineProperties(exports, {
defaultSession: { defaultSession: {
enumerable: true, enumerable: true,
get () { return fromPartition('') } get () { return fromPartition(''); }
}, },
fromPartition: { fromPartition: {
enumerable: true, enumerable: true,
value: fromPartition value: fromPartition
} }
}) });
Object.setPrototypeOf(Session.prototype, EventEmitter.prototype) Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype);
Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype) Object.setPrototypeOf(ServiceWorkerContext.prototype, EventEmitter.prototype);
Object.setPrototypeOf(Session.prototype, EventEmitter.prototype);
Session.prototype._init = function () { Session.prototype._init = function () {
app.emit('session-created', this) app.emit('session-created', this);
} };
const _originalStartLogging = NetLog.prototype.startLogging const _originalStartLogging = NetLog.prototype.startLogging;
NetLog.prototype.startLogging = function (path, ...args) { NetLog.prototype.startLogging = function (path, ...args) {
this._currentlyLoggingPath = path this._currentlyLoggingPath = path;
try { try {
return _originalStartLogging.call(this, path, ...args) return _originalStartLogging.call(this, path, ...args);
} catch (e) { } catch (e) {
this._currentlyLoggingPath = null this._currentlyLoggingPath = null;
throw e throw e;
}
} }
};
const _originalStopLogging = NetLog.prototype.stopLogging const _originalStopLogging = NetLog.prototype.stopLogging;
NetLog.prototype.stopLogging = function () { NetLog.prototype.stopLogging = function () {
const logPath = this._currentlyLoggingPath const logPath = this._currentlyLoggingPath;
this._currentlyLoggingPath = null this._currentlyLoggingPath = null;
return _originalStopLogging.call(this).then(() => logPath) return _originalStopLogging.call(this).then(() => logPath);
} };
const currentlyLoggingPathDeprecated = deprecate.warnOnce('currentlyLoggingPath') const currentlyLoggingPathDeprecated = deprecate.warnOnce('currentlyLoggingPath');
Object.defineProperties(NetLog.prototype, { Object.defineProperties(NetLog.prototype, {
currentlyLoggingPath: { currentlyLoggingPath: {
enumerable: true, enumerable: true,
get () { get () {
currentlyLoggingPathDeprecated() currentlyLoggingPathDeprecated();
return this._currentlyLoggingPath == null ? '' : this._currentlyLoggingPath return this._currentlyLoggingPath == null ? '' : this._currentlyLoggingPath;
} }
} }
}) });

View File

@@ -1,42 +1,41 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
import { deprecate } from 'electron' import { deprecate } from 'electron';
const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences') const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences');
// SystemPreferences is an EventEmitter. // SystemPreferences is an EventEmitter.
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype) Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype);
EventEmitter.call(systemPreferences) EventEmitter.call(systemPreferences);
if ('appLevelAppearance' in systemPreferences) { if ('getAppLevelAppearance' in systemPreferences) {
deprecate.fnToProperty( const nativeALAGetter = systemPreferences.getAppLevelAppearance;
SystemPreferences.prototype, const nativeALASetter = systemPreferences.setAppLevelAppearance;
'appLevelAppearance', Object.defineProperty(SystemPreferences.prototype, 'appLevelAppearance', {
'_getAppLevelAppearance', get: () => nativeALAGetter.call(systemPreferences),
'_setAppLevelAppearance' set: (appearance) => nativeALASetter.call(systemPreferences, appearance)
) });
} }
if ('effectiveAppearance' in systemPreferences) { if ('getEffectiveAppearance' in systemPreferences) {
deprecate.fnToProperty( const nativeEAGetter = systemPreferences.getAppLevelAppearance;
SystemPreferences.prototype, Object.defineProperty(SystemPreferences.prototype, 'effectiveAppearance', {
'effectiveAppearance', get: () => nativeEAGetter.call(systemPreferences)
'_getEffectiveAppearance' });
)
} }
SystemPreferences.prototype.isDarkMode = deprecate.moveAPI( SystemPreferences.prototype.isDarkMode = deprecate.moveAPI(
SystemPreferences.prototype.isDarkMode, SystemPreferences.prototype.isDarkMode,
'systemPreferences.isDarkMode()', 'systemPreferences.isDarkMode()',
'nativeTheme.shouldUseDarkColors' 'nativeTheme.shouldUseDarkColors'
) );
SystemPreferences.prototype.isInvertedColorScheme = deprecate.moveAPI( SystemPreferences.prototype.isInvertedColorScheme = deprecate.moveAPI(
SystemPreferences.prototype.isInvertedColorScheme, SystemPreferences.prototype.isInvertedColorScheme,
'systemPreferences.isInvertedColorScheme()', 'systemPreferences.isInvertedColorScheme()',
'nativeTheme.shouldUseInvertedColorScheme' 'nativeTheme.shouldUseInvertedColorScheme'
) );
SystemPreferences.prototype.isHighContrastColorScheme = deprecate.moveAPI( SystemPreferences.prototype.isHighContrastColorScheme = deprecate.moveAPI(
SystemPreferences.prototype.isHighContrastColorScheme, SystemPreferences.prototype.isHighContrastColorScheme,
'systemPreferences.isHighContrastColorScheme()', 'systemPreferences.isHighContrastColorScheme()',
'nativeTheme.shouldUseHighContrastColors' 'nativeTheme.shouldUseHighContrastColors'
) );
module.exports = systemPreferences module.exports = systemPreferences;

View File

@@ -1,24 +1,24 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { TopLevelWindow } = process.electronBinding('top_level_window') const { TopLevelWindow } = process.electronBinding('top_level_window');
Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype) Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype);
TopLevelWindow.prototype._init = function () { TopLevelWindow.prototype._init = function () {
// Avoid recursive require. // Avoid recursive require.
const { app } = electron const { app } = electron;
// Simulate the application menu on platforms other than macOS. // Simulate the application menu on platforms other than macOS.
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
const menu = app.applicationMenu const menu = app.applicationMenu;
if (menu) this.setMenu(menu) if (menu) this.setMenu(menu);
}
} }
};
TopLevelWindow.getFocusedWindow = () => { TopLevelWindow.getFocusedWindow = () => {
return TopLevelWindow.getAllWindows().find((win) => win.isFocused()) return TopLevelWindow.getAllWindows().find((win) => win.isFocused());
} };
module.exports = TopLevelWindow module.exports = TopLevelWindow;

View File

@@ -1,337 +1,349 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
let nextItemID = 1 let nextItemID = 1;
class TouchBar extends EventEmitter { class TouchBar extends EventEmitter {
// Bind a touch bar to a window // Bind a touch bar to a window
static _setOnWindow (touchBar, window) { static _setOnWindow (touchBar, window) {
if (window._touchBar != null) { if (window._touchBar != null) {
window._touchBar._removeFromWindow(window) window._touchBar._removeFromWindow(window);
} }
if (touchBar == null) { if (touchBar == null) {
window._setTouchBarItems([]) window._setTouchBarItems([]);
return return;
} }
if (Array.isArray(touchBar)) { if (Array.isArray(touchBar)) {
touchBar = new TouchBar(touchBar) touchBar = new TouchBar(touchBar);
} }
touchBar._addToWindow(window) touchBar._addToWindow(window);
} }
constructor (options) { constructor (options) {
super() super();
if (options == null) { if (options == null) {
throw new Error('Must specify options object as first argument') throw new Error('Must specify options object as first argument');
} }
let { items, escapeItem } = options let { items, escapeItem } = options;
if (!Array.isArray(items)) { if (!Array.isArray(items)) {
items = [] items = [];
} }
this.changeListener = (item) => { this.changeListener = (item) => {
this.emit('change', item.id, item.type) this.emit('change', item.id, item.type);
} };
this.windowListeners = {} this.windowListeners = {};
this.items = {} this.items = {};
this.ordereredItems = [] this.ordereredItems = [];
this.escapeItem = escapeItem this.escapeItem = escapeItem;
const registerItem = (item) => { const registerItem = (item) => {
this.items[item.id] = item this.items[item.id] = item;
item.on('change', this.changeListener) item.on('change', this.changeListener);
if (item.child instanceof TouchBar) { if (item.child instanceof TouchBar) {
item.child.ordereredItems.forEach(registerItem) item.child.ordereredItems.forEach(registerItem);
}
} }
};
const idSet = new Set();
items.forEach((item) => { items.forEach((item) => {
if (!(item instanceof TouchBarItem)) { if (!(item instanceof TouchBarItem)) {
throw new Error('Each item must be an instance of TouchBarItem') throw new Error('Each item must be an instance of TouchBarItem');
}
if (!idSet.has(item.id)) {
idSet.add(item.id);
} else {
throw new Error('Cannot add a single instance of TouchBarItem multiple times in a TouchBar');
}
});
// register in separate loop after all items are validated
for (const item of items) {
this.ordereredItems.push(item);
registerItem(item);
} }
this.ordereredItems.push(item)
registerItem(item)
})
} }
set escapeItem (item) { set escapeItem (item) {
if (item != null && !(item instanceof TouchBarItem)) { if (item != null && !(item instanceof TouchBarItem)) {
throw new Error('Escape item must be an instance of TouchBarItem') throw new Error('Escape item must be an instance of TouchBarItem');
} }
if (this.escapeItem != null) { if (this.escapeItem != null) {
this.escapeItem.removeListener('change', this.changeListener) this.escapeItem.removeListener('change', this.changeListener);
} }
this._escapeItem = item this._escapeItem = item;
if (this.escapeItem != null) { if (this.escapeItem != null) {
this.escapeItem.on('change', this.changeListener) this.escapeItem.on('change', this.changeListener);
} }
this.emit('escape-item-change', item) this.emit('escape-item-change', item);
} }
get escapeItem () { get escapeItem () {
return this._escapeItem return this._escapeItem;
} }
_addToWindow (window) { _addToWindow (window) {
const { id } = window const { id } = window;
// Already added to window // Already added to window
if (this.windowListeners.hasOwnProperty(id)) return if (this.windowListeners.hasOwnProperty(id)) return;
window._touchBar = this window._touchBar = this;
const changeListener = (itemID) => { const changeListener = (itemID) => {
window._refreshTouchBarItem(itemID) window._refreshTouchBarItem(itemID);
} };
this.on('change', changeListener) this.on('change', changeListener);
const escapeItemListener = (item) => { const escapeItemListener = (item) => {
window._setEscapeTouchBarItem(item != null ? item : {}) window._setEscapeTouchBarItem(item != null ? item : {});
} };
this.on('escape-item-change', escapeItemListener) this.on('escape-item-change', escapeItemListener);
const interactionListener = (event, itemID, details) => { const interactionListener = (event, itemID, details) => {
let item = this.items[itemID] let item = this.items[itemID];
if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) { if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) {
item = this.escapeItem item = this.escapeItem;
} }
if (item != null && item.onInteraction != null) { if (item != null && item.onInteraction != null) {
item.onInteraction(details) item.onInteraction(details);
} }
} };
window.on('-touch-bar-interaction', interactionListener) window.on('-touch-bar-interaction', interactionListener);
const removeListeners = () => { const removeListeners = () => {
this.removeListener('change', changeListener) this.removeListener('change', changeListener);
this.removeListener('escape-item-change', escapeItemListener) this.removeListener('escape-item-change', escapeItemListener);
window.removeListener('-touch-bar-interaction', interactionListener) window.removeListener('-touch-bar-interaction', interactionListener);
window.removeListener('closed', removeListeners) window.removeListener('closed', removeListeners);
window._touchBar = null window._touchBar = null;
delete this.windowListeners[id] delete this.windowListeners[id];
const unregisterItems = (items) => { const unregisterItems = (items) => {
for (const item of items) { for (const item of items) {
item.removeListener('change', this.changeListener) item.removeListener('change', this.changeListener);
if (item.child instanceof TouchBar) { if (item.child instanceof TouchBar) {
unregisterItems(item.child.ordereredItems) unregisterItems(item.child.ordereredItems);
} }
} }
} };
unregisterItems(this.ordereredItems) unregisterItems(this.ordereredItems);
if (this.escapeItem) { if (this.escapeItem) {
this.escapeItem.removeListener('change', this.changeListener) this.escapeItem.removeListener('change', this.changeListener);
} }
} };
window.once('closed', removeListeners) window.once('closed', removeListeners);
this.windowListeners[id] = removeListeners this.windowListeners[id] = removeListeners;
window._setTouchBarItems(this.ordereredItems) window._setTouchBarItems(this.ordereredItems);
escapeItemListener(this.escapeItem) escapeItemListener(this.escapeItem);
} }
_removeFromWindow (window) { _removeFromWindow (window) {
const removeListeners = this.windowListeners[window.id] const removeListeners = this.windowListeners[window.id];
if (removeListeners != null) removeListeners() if (removeListeners != null) removeListeners();
} }
} }
class TouchBarItem extends EventEmitter { class TouchBarItem extends EventEmitter {
constructor () { constructor () {
super() super();
this._addImmutableProperty('id', `${nextItemID++}`) this._addImmutableProperty('id', `${nextItemID++}`);
this._parents = [] this._parents = [];
} }
_addImmutableProperty (name, value) { _addImmutableProperty (name, value) {
Object.defineProperty(this, name, { Object.defineProperty(this, name, {
get: function () { get: function () {
return value return value;
}, },
set: function () { set: function () {
throw new Error(`Cannot override property ${name}`) throw new Error(`Cannot override property ${name}`);
}, },
enumerable: true, enumerable: true,
configurable: false configurable: false
}) });
} }
_addLiveProperty (name, initialValue) { _addLiveProperty (name, initialValue) {
const privateName = `_${name}` const privateName = `_${name}`;
this[privateName] = initialValue this[privateName] = initialValue;
Object.defineProperty(this, name, { Object.defineProperty(this, name, {
get: function () { get: function () {
return this[privateName] return this[privateName];
}, },
set: function (value) { set: function (value) {
this[privateName] = value this[privateName] = value;
this.emit('change', this) this.emit('change', this);
}, },
enumerable: true enumerable: true
}) });
} }
_addParent (item) { _addParent (item) {
const existing = this._parents.some(test => test.id === item.id) const existing = this._parents.some(test => test.id === item.id);
if (!existing) { if (!existing) {
this._parents.push({ this._parents.push({
id: item.id, id: item.id,
type: item.type type: item.type
}) });
} }
} }
} }
TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'button') this._addImmutableProperty('type', 'button');
this._addLiveProperty('label', config.label) this._addLiveProperty('label', config.label);
this._addLiveProperty('accessibilityLabel', config.accessibilityLabel) this._addLiveProperty('accessibilityLabel', config.accessibilityLabel);
this._addLiveProperty('backgroundColor', config.backgroundColor) this._addLiveProperty('backgroundColor', config.backgroundColor);
this._addLiveProperty('icon', config.icon) this._addLiveProperty('icon', config.icon);
this._addLiveProperty('iconPosition', config.iconPosition) this._addLiveProperty('iconPosition', config.iconPosition);
this._addLiveProperty('enabled', typeof config.enabled !== 'boolean' ? true : config.enabled) this._addLiveProperty('enabled', typeof config.enabled !== 'boolean' ? true : config.enabled);
if (typeof config.click === 'function') { if (typeof config.click === 'function') {
this._addImmutableProperty('onInteraction', () => { this._addImmutableProperty('onInteraction', () => {
config.click() config.click();
}) });
}
} }
} }
};
TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem { TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'colorpicker') this._addImmutableProperty('type', 'colorpicker');
this._addLiveProperty('availableColors', config.availableColors) this._addLiveProperty('availableColors', config.availableColors);
this._addLiveProperty('selectedColor', config.selectedColor) this._addLiveProperty('selectedColor', config.selectedColor);
if (typeof config.change === 'function') { if (typeof config.change === 'function') {
this._addImmutableProperty('onInteraction', (details) => { this._addImmutableProperty('onInteraction', (details) => {
this._selectedColor = details.color this._selectedColor = details.color;
config.change(details.color) config.change(details.color);
}) });
}
} }
} }
};
TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem { TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'group') this._addImmutableProperty('type', 'group');
const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items) const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items);
this._addLiveProperty('child', defaultChild) this._addLiveProperty('child', defaultChild);
this.child.ordereredItems.forEach((item) => item._addParent(this)) this.child.ordereredItems.forEach((item) => item._addParent(this));
}
} }
};
TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'label') this._addImmutableProperty('type', 'label');
this._addLiveProperty('label', config.label) this._addLiveProperty('label', config.label);
this._addLiveProperty('accessibilityLabel', config.accessibilityLabel) this._addLiveProperty('accessibilityLabel', config.accessibilityLabel);
this._addLiveProperty('textColor', config.textColor) this._addLiveProperty('textColor', config.textColor);
}
} }
};
TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'popover') this._addImmutableProperty('type', 'popover');
this._addLiveProperty('label', config.label) this._addLiveProperty('label', config.label);
this._addLiveProperty('icon', config.icon) this._addLiveProperty('icon', config.icon);
this._addLiveProperty('showCloseButton', config.showCloseButton) this._addLiveProperty('showCloseButton', config.showCloseButton);
const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items) const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items);
this._addLiveProperty('child', defaultChild) this._addLiveProperty('child', defaultChild);
this.child.ordereredItems.forEach((item) => item._addParent(this)) this.child.ordereredItems.forEach((item) => item._addParent(this));
}
} }
};
TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'slider') this._addImmutableProperty('type', 'slider');
this._addLiveProperty('label', config.label) this._addLiveProperty('label', config.label);
this._addLiveProperty('minValue', config.minValue) this._addLiveProperty('minValue', config.minValue);
this._addLiveProperty('maxValue', config.maxValue) this._addLiveProperty('maxValue', config.maxValue);
this._addLiveProperty('value', config.value) this._addLiveProperty('value', config.value);
if (typeof config.change === 'function') { if (typeof config.change === 'function') {
this._addImmutableProperty('onInteraction', (details) => { this._addImmutableProperty('onInteraction', (details) => {
this._value = details.value this._value = details.value;
config.change(details.value) config.change(details.value);
}) });
}
} }
} }
};
TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'spacer') this._addImmutableProperty('type', 'spacer');
this._addImmutableProperty('size', config.size) this._addImmutableProperty('size', config.size);
}
} }
};
TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem { TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
this._addImmutableProperty('type', 'segmented_control') this._addImmutableProperty('type', 'segmented_control');
this._addLiveProperty('segmentStyle', config.segmentStyle) this._addLiveProperty('segmentStyle', config.segmentStyle);
this._addLiveProperty('segments', config.segments || []) this._addLiveProperty('segments', config.segments || []);
this._addLiveProperty('selectedIndex', config.selectedIndex) this._addLiveProperty('selectedIndex', config.selectedIndex);
this._addLiveProperty('mode', config.mode) this._addLiveProperty('mode', config.mode);
if (typeof config.change === 'function') { if (typeof config.change === 'function') {
this._addImmutableProperty('onInteraction', (details) => { this._addImmutableProperty('onInteraction', (details) => {
this._selectedIndex = details.selectedIndex this._selectedIndex = details.selectedIndex;
config.change(details.selectedIndex, details.isSelected) config.change(details.selectedIndex, details.isSelected);
}) });
}
} }
} }
};
TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem {
constructor (config) { constructor (config) {
super() super();
if (config == null) config = {} if (config == null) config = {};
let { select, highlight } = config let { select, highlight } = config;
this._addImmutableProperty('type', 'scrubber') this._addImmutableProperty('type', 'scrubber');
this._addLiveProperty('items', config.items) this._addLiveProperty('items', config.items);
this._addLiveProperty('selectedStyle', config.selectedStyle || null) this._addLiveProperty('selectedStyle', config.selectedStyle || null);
this._addLiveProperty('overlayStyle', config.overlayStyle || null) this._addLiveProperty('overlayStyle', config.overlayStyle || null);
this._addLiveProperty('showArrowButtons', config.showArrowButtons || false) this._addLiveProperty('showArrowButtons', config.showArrowButtons || false);
this._addLiveProperty('mode', config.mode || 'free') this._addLiveProperty('mode', config.mode || 'free');
const cont = typeof config.continuous === 'undefined' ? true : config.continuous const cont = typeof config.continuous === 'undefined' ? true : config.continuous;
this._addLiveProperty('continuous', cont) this._addLiveProperty('continuous', cont);
if (typeof select === 'function' || typeof highlight === 'function') { if (typeof select === 'function' || typeof highlight === 'function') {
if (select == null) select = () => {} if (select == null) select = () => {};
if (highlight == null) highlight = () => {} if (highlight == null) highlight = () => {};
this._addImmutableProperty('onInteraction', (details) => { this._addImmutableProperty('onInteraction', (details) => {
if (details.type === 'select' && typeof select === 'function') { if (details.type === 'select' && typeof select === 'function') {
select(details.selectedIndex) select(details.selectedIndex);
} else if (details.type === 'highlight' && typeof highlight === 'function') { } else if (details.type === 'highlight' && typeof highlight === 'function') {
highlight(details.highlightedIndex) highlight(details.highlightedIndex);
}
})
} }
});
} }
} }
};
module.exports = TouchBar module.exports = TouchBar;

View File

@@ -1,9 +1,9 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { deprecate } = require('electron') const { deprecate } = require('electron');
const { Tray } = process.electronBinding('tray') const { Tray } = process.electronBinding('tray');
Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype) Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype);
module.exports = Tray module.exports = Tray;

View File

@@ -1,11 +1,11 @@
'use strict' 'use strict';
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const { View } = process.electronBinding('view') const { View } = process.electronBinding('view');
Object.setPrototypeOf(View.prototype, EventEmitter.prototype) Object.setPrototypeOf(View.prototype, EventEmitter.prototype);
View.prototype._init = function () { View.prototype._init = function () {
} };
module.exports = View module.exports = View;

View File

@@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { LayoutManager } = electron const { LayoutManager } = electron;
const { BoxLayout } = process.electronBinding('box_layout') const { BoxLayout } = process.electronBinding('box_layout');
Object.setPrototypeOf(BoxLayout.prototype, LayoutManager.prototype) Object.setPrototypeOf(BoxLayout.prototype, LayoutManager.prototype);
BoxLayout.prototype._init = function () { BoxLayout.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
LayoutManager.prototype._init.call(this) LayoutManager.prototype._init.call(this);
} };
module.exports = BoxLayout module.exports = BoxLayout;

View File

@@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { View } = electron const { View } = electron;
const { Button } = process.electronBinding('button') const { Button } = process.electronBinding('button');
Object.setPrototypeOf(Button.prototype, View.prototype) Object.setPrototypeOf(Button.prototype, View.prototype);
Button.prototype._init = function () { Button.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
View.prototype._init.call(this) View.prototype._init.call(this);
} };
module.exports = Button module.exports = Button;

View File

@@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { Button } = electron const { Button } = electron;
const { LabelButton } = process.electronBinding('label_button') const { LabelButton } = process.electronBinding('label_button');
Object.setPrototypeOf(LabelButton.prototype, Button.prototype) Object.setPrototypeOf(LabelButton.prototype, Button.prototype);
LabelButton.prototype._init = function () { LabelButton.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
Button.prototype._init.call(this) Button.prototype._init.call(this);
} };
module.exports = LabelButton module.exports = LabelButton;

View File

@@ -1,8 +1,8 @@
'use strict' 'use strict';
const { LayoutManager } = process.electronBinding('layout_manager') const { LayoutManager } = process.electronBinding('layout_manager');
LayoutManager.prototype._init = function () { LayoutManager.prototype._init = function () {
} };
module.exports = LayoutManager module.exports = LayoutManager;

View File

@@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { LabelButton } = electron const { LabelButton } = electron;
const { MdTextButton } = process.electronBinding('md_text_button') const { MdTextButton } = process.electronBinding('md_text_button');
Object.setPrototypeOf(MdTextButton.prototype, LabelButton.prototype) Object.setPrototypeOf(MdTextButton.prototype, LabelButton.prototype);
MdTextButton.prototype._init = function () { MdTextButton.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
LabelButton.prototype._init.call(this) LabelButton.prototype._init.call(this);
} };
module.exports = MdTextButton module.exports = MdTextButton;

View File

@@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { View } = electron const { View } = electron;
const { ResizeArea } = process.electronBinding('resize_area') const { ResizeArea } = process.electronBinding('resize_area');
Object.setPrototypeOf(ResizeArea.prototype, View.prototype) Object.setPrototypeOf(ResizeArea.prototype, View.prototype);
ResizeArea.prototype._init = function () { ResizeArea.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
View.prototype._init.call(this) View.prototype._init.call(this);
} };
module.exports = ResizeArea module.exports = ResizeArea;

View File

@@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { View } = electron const { View } = electron;
const { TextField } = process.electronBinding('text_field') const { TextField } = process.electronBinding('text_field');
Object.setPrototypeOf(TextField.prototype, View.prototype) Object.setPrototypeOf(TextField.prototype, View.prototype);
TextField.prototype._init = function () { TextField.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
View.prototype._init.call(this) View.prototype._init.call(this);
} };
module.exports = TextField module.exports = TextField;

View File

@@ -1,15 +1,15 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { View } = electron const { View } = electron;
const { WebContentsView } = process.electronBinding('web_contents_view') const { WebContentsView } = process.electronBinding('web_contents_view');
Object.setPrototypeOf(WebContentsView.prototype, View.prototype) Object.setPrototypeOf(WebContentsView.prototype, View.prototype);
WebContentsView.prototype._init = function () { WebContentsView.prototype._init = function () {
// Call parent class's _init. // Call parent class's _init.
View.prototype._init.call(this) View.prototype._init.call(this);
} };
module.exports = WebContentsView module.exports = WebContentsView;

View File

@@ -1,26 +1,26 @@
'use strict' 'use strict';
const features = process.electronBinding('features') const features = process.electronBinding('features');
const { EventEmitter } = require('events') const { EventEmitter } = require('events');
const electron = require('electron') const electron = require('electron');
const path = require('path') const path = require('path');
const url = require('url') const url = require('url');
const { app, ipcMain, session, deprecate } = electron const { app, ipcMain, session, deprecate } = electron;
const { internalWindowOpen } = require('@electron/internal/browser/guest-window-manager') const { internalWindowOpen } = require('@electron/internal/browser/guest-window-manager');
const NavigationController = require('@electron/internal/browser/navigation-controller') const NavigationController = require('@electron/internal/browser/navigation-controller');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
// session is not used here, the purpose is to make sure session is initalized // session is not used here, the purpose is to make sure session is initalized
// before the webContents module. // before the webContents module.
// eslint-disable-next-line // eslint-disable-next-line
session session
let nextId = 0 let nextId = 0;
const getNextId = function () { const getNextId = function () {
return ++nextId return ++nextId;
} };
// Stock page sizes // Stock page sizes
const PDFPageSizes = { const PDFPageSizes = {
@@ -61,7 +61,7 @@ const PDFPageSizes = {
width_microns: 279400, width_microns: 279400,
custom_display_name: 'Tabloid' custom_display_name: 'Tabloid'
} }
} };
// Default printing setting // Default printing setting
const defaultPrintingSetting = { const defaultPrintingSetting = {
@@ -93,83 +93,83 @@ const defaultPrintingSetting = {
// 2 = color - see ColorModel in //printing/print_job_constants.h // 2 = color - see ColorModel in //printing/print_job_constants.h
color: 2, color: 2,
collate: true collate: true
} };
// JavaScript implementations of WebContents. // JavaScript implementations of WebContents.
const binding = process.electronBinding('web_contents') const binding = process.electronBinding('web_contents');
const { WebContents } = binding const { WebContents } = binding;
Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype) Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype);
Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype) Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype);
// WebContents::send(channel, args..) // WebContents::send(channel, args..)
// WebContents::sendToAll(channel, args..) // WebContents::sendToAll(channel, args..)
WebContents.prototype.send = function (channel, ...args) { WebContents.prototype.send = function (channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} }
const internal = false const internal = false;
const sendToAll = false const sendToAll = false;
return this._send(internal, sendToAll, channel, args) return this._send(internal, sendToAll, channel, args);
} };
WebContents.prototype.sendToAll = function (channel, ...args) { WebContents.prototype.sendToAll = function (channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} }
const internal = false const internal = false;
const sendToAll = true const sendToAll = true;
return this._send(internal, sendToAll, channel, args) return this._send(internal, sendToAll, channel, args);
} };
WebContents.prototype._sendInternal = function (channel, ...args) { WebContents.prototype._sendInternal = function (channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} }
const internal = true const internal = true;
const sendToAll = false const sendToAll = false;
return this._send(internal, sendToAll, channel, args) return this._send(internal, sendToAll, channel, args);
} };
WebContents.prototype._sendInternalToAll = function (channel, ...args) { WebContents.prototype._sendInternalToAll = function (channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} }
const internal = true const internal = true;
const sendToAll = true const sendToAll = true;
return this._send(internal, sendToAll, channel, args) return this._send(internal, sendToAll, channel, args);
} };
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) { WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} else if (typeof frameId !== 'number') { } else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument') throw new Error('Missing required frameId argument');
} }
const internal = false const internal = false;
const sendToAll = false const sendToAll = false;
return this._sendToFrame(internal, sendToAll, frameId, channel, args) return this._sendToFrame(internal, sendToAll, frameId, channel, args);
} };
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) { WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
if (typeof channel !== 'string') { if (typeof channel !== 'string') {
throw new Error('Missing required channel argument') throw new Error('Missing required channel argument');
} else if (typeof frameId !== 'number') { } else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument') throw new Error('Missing required frameId argument');
} }
const internal = true const internal = true;
const sendToAll = false const sendToAll = false;
return this._sendToFrame(internal, sendToAll, frameId, channel, args) return this._sendToFrame(internal, sendToAll, frameId, channel, args);
} };
// Following methods are mapped to webFrame. // Following methods are mapped to webFrame.
const webFrameMethods = [ const webFrameMethods = [
@@ -177,138 +177,138 @@ const webFrameMethods = [
'insertText', 'insertText',
'removeInsertedCSS', 'removeInsertedCSS',
'setVisualZoomLevelLimits' 'setVisualZoomLevelLimits'
] ];
for (const method of webFrameMethods) { for (const method of webFrameMethods) {
WebContents.prototype[method] = function (...args) { WebContents.prototype[method] = function (...args) {
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args) return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args);
} };
} }
const waitTillCanExecuteJavaScript = async (webContents) => { const waitTillCanExecuteJavaScript = async (webContents) => {
if (webContents.getURL() && !webContents.isLoadingMainFrame()) return if (webContents.getURL() && !webContents.isLoadingMainFrame()) return;
return new Promise((resolve) => { return new Promise((resolve) => {
webContents.once('did-stop-loading', () => { webContents.once('did-stop-loading', () => {
resolve() resolve();
}) });
}) });
} };
// Make sure WebContents::executeJavaScript would run the code only when the // Make sure WebContents::executeJavaScript would run the code only when the
// WebContents has been loaded. // WebContents has been loaded.
WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) { WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) {
await waitTillCanExecuteJavaScript(this) await waitTillCanExecuteJavaScript(this);
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture) return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture);
} };
WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (code, hasUserGesture) { WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (code, hasUserGesture) {
await waitTillCanExecuteJavaScript(this) await waitTillCanExecuteJavaScript(this);
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScriptInIsolatedWorld', code, hasUserGesture) return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScriptInIsolatedWorld', code, hasUserGesture);
} };
// Translate the options of printToPDF. // Translate the options of printToPDF.
WebContents.prototype.printToPDF = function (options) { WebContents.prototype.printToPDF = function (options) {
const printSettings = { const printSettings = {
...defaultPrintingSetting, ...defaultPrintingSetting,
requestID: getNextId() requestID: getNextId()
} };
if (options.landscape !== undefined) { if (options.landscape !== undefined) {
if (typeof options.landscape !== 'boolean') { if (typeof options.landscape !== 'boolean') {
const error = new Error('landscape must be a Boolean') const error = new Error('landscape must be a Boolean');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.landscape = options.landscape printSettings.landscape = options.landscape;
} }
if (options.scaleFactor !== undefined) { if (options.scaleFactor !== undefined) {
if (typeof options.scaleFactor !== 'number') { if (typeof options.scaleFactor !== 'number') {
const error = new Error('scaleFactor must be a Number') const error = new Error('scaleFactor must be a Number');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.scaleFactor = options.scaleFactor printSettings.scaleFactor = options.scaleFactor;
} }
if (options.marginsType !== undefined) { if (options.marginsType !== undefined) {
if (typeof options.marginsType !== 'number') { if (typeof options.marginsType !== 'number') {
const error = new Error('marginsType must be a Number') const error = new Error('marginsType must be a Number');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.marginsType = options.marginsType printSettings.marginsType = options.marginsType;
} }
if (options.printSelectionOnly !== undefined) { if (options.printSelectionOnly !== undefined) {
if (typeof options.printSelectionOnly !== 'boolean') { if (typeof options.printSelectionOnly !== 'boolean') {
const error = new Error('printSelectionOnly must be a Boolean') const error = new Error('printSelectionOnly must be a Boolean');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.shouldPrintSelectionOnly = options.printSelectionOnly printSettings.shouldPrintSelectionOnly = options.printSelectionOnly;
} }
if (options.printBackground !== undefined) { if (options.printBackground !== undefined) {
if (typeof options.printBackground !== 'boolean') { if (typeof options.printBackground !== 'boolean') {
const error = new Error('printBackground must be a Boolean') const error = new Error('printBackground must be a Boolean');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.shouldPrintBackgrounds = options.printBackground printSettings.shouldPrintBackgrounds = options.printBackground;
} }
if (options.pageRanges !== undefined) { if (options.pageRanges !== undefined) {
const pageRanges = options.pageRanges const pageRanges = options.pageRanges;
if (!pageRanges.hasOwnProperty('from') || !pageRanges.hasOwnProperty('to')) { if (!pageRanges.hasOwnProperty('from') || !pageRanges.hasOwnProperty('to')) {
const error = new Error(`pageRanges must be an Object with 'from' and 'to' properties`) const error = new Error(`pageRanges must be an Object with 'from' and 'to' properties`);
return Promise.reject(error) return Promise.reject(error);
} }
if (typeof pageRanges.from !== 'number') { if (typeof pageRanges.from !== 'number') {
const error = new Error('pageRanges.from must be a Number') const error = new Error('pageRanges.from must be a Number');
return Promise.reject(error) return Promise.reject(error);
} }
if (typeof pageRanges.to !== 'number') { if (typeof pageRanges.to !== 'number') {
const error = new Error('pageRanges.to must be a Number') const error = new Error('pageRanges.to must be a Number');
return Promise.reject(error) return Promise.reject(error);
} }
// Chromium uses 1-based page ranges, so increment each by 1. // Chromium uses 1-based page ranges, so increment each by 1.
printSettings.pageRange = [{ printSettings.pageRange = [{
from: pageRanges.from + 1, from: pageRanges.from + 1,
to: pageRanges.to + 1 to: pageRanges.to + 1
}] }];
} }
if (options.headerFooter !== undefined) { if (options.headerFooter !== undefined) {
const headerFooter = options.headerFooter const headerFooter = options.headerFooter;
printSettings.headerFooterEnabled = true printSettings.headerFooterEnabled = true;
if (typeof headerFooter === 'object') { if (typeof headerFooter === 'object') {
if (!headerFooter.url || !headerFooter.title) { if (!headerFooter.url || !headerFooter.title) {
const error = new Error('url and title properties are required for headerFooter') const error = new Error('url and title properties are required for headerFooter');
return Promise.reject(error) return Promise.reject(error);
} }
if (typeof headerFooter.title !== 'string') { if (typeof headerFooter.title !== 'string') {
const error = new Error('headerFooter.title must be a String') const error = new Error('headerFooter.title must be a String');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.title = headerFooter.title printSettings.title = headerFooter.title;
if (typeof headerFooter.url !== 'string') { if (typeof headerFooter.url !== 'string') {
const error = new Error('headerFooter.url must be a String') const error = new Error('headerFooter.url must be a String');
return Promise.reject(error) return Promise.reject(error);
} }
printSettings.url = headerFooter.url printSettings.url = headerFooter.url;
} else { } else {
const error = new Error('headerFooter must be an Object') const error = new Error('headerFooter must be an Object');
return Promise.reject(error) return Promise.reject(error);
} }
} }
// Optionally set size for PDF. // Optionally set size for PDF.
if (options.pageSize !== undefined) { if (options.pageSize !== undefined) {
const pageSize = options.pageSize const pageSize = options.pageSize;
if (typeof pageSize === 'object') { if (typeof pageSize === 'object') {
if (!pageSize.height || !pageSize.width) { if (!pageSize.height || !pageSize.width) {
const error = new Error('height and width properties are required for pageSize') const error = new Error('height and width properties are required for pageSize');
return Promise.reject(error) return Promise.reject(error);
} }
// Dimensions in Microns // Dimensions in Microns
// 1 meter = 10^6 microns // 1 meter = 10^6 microns
@@ -317,28 +317,28 @@ WebContents.prototype.printToPDF = function (options) {
custom_display_name: 'Custom', custom_display_name: 'Custom',
height_microns: Math.ceil(pageSize.height), height_microns: Math.ceil(pageSize.height),
width_microns: Math.ceil(pageSize.width) width_microns: Math.ceil(pageSize.width)
} };
} else if (PDFPageSizes[pageSize]) { } else if (PDFPageSizes[pageSize]) {
printSettings.mediaSize = PDFPageSizes[pageSize] printSettings.mediaSize = PDFPageSizes[pageSize];
} else { } else {
const error = new Error(`Unsupported pageSize: ${pageSize}`) const error = new Error(`Unsupported pageSize: ${pageSize}`);
return Promise.reject(error) return Promise.reject(error);
} }
} else { } else {
printSettings.mediaSize = PDFPageSizes['A4'] printSettings.mediaSize = PDFPageSizes['A4'];
} }
// Chromium expects this in a 0-100 range number, not as float // Chromium expects this in a 0-100 range number, not as float
printSettings.scaleFactor = Math.ceil(printSettings.scaleFactor) % 100 printSettings.scaleFactor = Math.ceil(printSettings.scaleFactor) % 100;
// PrinterType enum from //printing/print_job_constants.h // PrinterType enum from //printing/print_job_constants.h
printSettings.printerType = 2 printSettings.printerType = 2;
if (features.isPrintingEnabled()) { if (features.isPrintingEnabled()) {
return this._printToPDF(printSettings) return this._printToPDF(printSettings);
} else { } else {
const error = new Error('Printing feature is disabled') const error = new Error('Printing feature is disabled');
return Promise.reject(error) return Promise.reject(error);
}
} }
};
WebContents.prototype.print = function (options = {}, callback) { WebContents.prototype.print = function (options = {}, callback) {
// TODO(codebytere): deduplicate argument sanitization by moving rest of // TODO(codebytere): deduplicate argument sanitization by moving rest of
@@ -346,10 +346,10 @@ WebContents.prototype.print = function (options = {}, callback) {
if (typeof options === 'object') { if (typeof options === 'object') {
// Optionally set size for PDF. // Optionally set size for PDF.
if (options.pageSize !== undefined) { if (options.pageSize !== undefined) {
const pageSize = options.pageSize const pageSize = options.pageSize;
if (typeof pageSize === 'object') { if (typeof pageSize === 'object') {
if (!pageSize.height || !pageSize.width) { if (!pageSize.height || !pageSize.width) {
throw new Error('height and width properties are required for pageSize') throw new Error('height and width properties are required for pageSize');
} }
// Dimensions in Microns - 1 meter = 10^6 microns // Dimensions in Microns - 1 meter = 10^6 microns
options.mediaSize = { options.mediaSize = {
@@ -357,40 +357,40 @@ WebContents.prototype.print = function (options = {}, callback) {
custom_display_name: 'Custom', custom_display_name: 'Custom',
height_microns: Math.ceil(pageSize.height), height_microns: Math.ceil(pageSize.height),
width_microns: Math.ceil(pageSize.width) width_microns: Math.ceil(pageSize.width)
} };
} else if (PDFPageSizes[pageSize]) { } else if (PDFPageSizes[pageSize]) {
options.mediaSize = PDFPageSizes[pageSize] options.mediaSize = PDFPageSizes[pageSize];
} else { } else {
throw new Error(`Unsupported pageSize: ${pageSize}`) throw new Error(`Unsupported pageSize: ${pageSize}`);
} }
} }
} }
if (features.isPrintingEnabled()) { if (features.isPrintingEnabled()) {
if (callback) { if (callback) {
this._print(options, callback) this._print(options, callback);
} else { } else {
this._print(options) this._print(options);
} }
} else { } else {
console.error('Error: Printing feature is disabled.') console.error('Error: Printing feature is disabled.');
}
} }
};
WebContents.prototype.getPrinters = function () { WebContents.prototype.getPrinters = function () {
if (features.isPrintingEnabled()) { if (features.isPrintingEnabled()) {
return this._getPrinters() return this._getPrinters();
} else { } else {
console.error('Error: Printing feature is disabled.') console.error('Error: Printing feature is disabled.');
return [] return [];
}
} }
};
WebContents.prototype.loadFile = function (filePath, options = {}) { WebContents.prototype.loadFile = function (filePath, options = {}) {
if (typeof filePath !== 'string') { if (typeof filePath !== 'string') {
throw new Error('Must pass filePath as a string') throw new Error('Must pass filePath as a string');
} }
const { query, search, hash } = options const { query, search, hash } = options;
return this.loadURL(url.format({ return this.loadURL(url.format({
protocol: 'file', protocol: 'file',
@@ -399,99 +399,99 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
query, query,
search, search,
hash hash
})) }));
} };
const addReplyToEvent = (event) => { const addReplyToEvent = (event) => {
event.reply = (...args) => { event.reply = (...args) => {
event.sender.sendToFrame(event.frameId, ...args) event.sender.sendToFrame(event.frameId, ...args);
} };
} };
const addReplyInternalToEvent = (event) => { const addReplyInternalToEvent = (event) => {
Object.defineProperty(event, '_replyInternal', { Object.defineProperty(event, '_replyInternal', {
configurable: false, configurable: false,
enumerable: false, enumerable: false,
value: (...args) => { value: (...args) => {
event.sender._sendToFrameInternal(event.frameId, ...args) event.sender._sendToFrameInternal(event.frameId, ...args);
}
})
} }
});
};
const addReturnValueToEvent = (event) => { const addReturnValueToEvent = (event) => {
Object.defineProperty(event, 'returnValue', { Object.defineProperty(event, 'returnValue', {
set: (value) => event.sendReply([value]), set: (value) => event.sendReply([value]),
get: () => {} get: () => {}
}) });
} };
// Add JavaScript wrappers for WebContents class. // Add JavaScript wrappers for WebContents class.
WebContents.prototype._init = function () { WebContents.prototype._init = function () {
// The navigation controller. // The navigation controller.
NavigationController.call(this, this) NavigationController.call(this, this);
// Every remote callback from renderer process would add a listener to the // Every remote callback from renderer process would add a listener to the
// render-view-deleted event, so ignore the listeners warning. // render-view-deleted event, so ignore the listeners warning.
this.setMaxListeners(0) this.setMaxListeners(0);
// Dispatch IPC messages to the ipc module. // Dispatch IPC messages to the ipc module.
this.on('-ipc-message', function (event, internal, channel, args) { this.on('-ipc-message', function (event, internal, channel, args) {
if (internal) { if (internal) {
addReplyInternalToEvent(event) addReplyInternalToEvent(event);
ipcMainInternal.emit(channel, event, ...args) ipcMainInternal.emit(channel, event, ...args);
} else { } else {
addReplyToEvent(event) addReplyToEvent(event);
this.emit('ipc-message', event, channel, ...args) this.emit('ipc-message', event, channel, ...args);
ipcMain.emit(channel, event, ...args) ipcMain.emit(channel, event, ...args);
} }
}) });
this.on('-ipc-invoke', function (event, internal, channel, args) { this.on('-ipc-invoke', function (event, internal, channel, args) {
event._reply = (result) => event.sendReply({ result }) event._reply = (result) => event.sendReply({ result });
event._throw = (error) => { event._throw = (error) => {
console.error(`Error occurred in handler for '${channel}':`, error) console.error(`Error occurred in handler for '${channel}':`, error);
event.sendReply({ error: error.toString() }) event.sendReply({ error: error.toString() });
} };
const target = internal ? ipcMainInternal : ipcMain const target = internal ? ipcMainInternal : ipcMain;
if (target._invokeHandlers.has(channel)) { if (target._invokeHandlers.has(channel)) {
target._invokeHandlers.get(channel)(event, ...args) target._invokeHandlers.get(channel)(event, ...args);
} else { } else {
event._throw(`No handler registered for '${channel}'`) event._throw(`No handler registered for '${channel}'`);
} }
}) });
this.on('-ipc-message-sync', function (event, internal, channel, args) { this.on('-ipc-message-sync', function (event, internal, channel, args) {
addReturnValueToEvent(event) addReturnValueToEvent(event);
if (internal) { if (internal) {
addReplyInternalToEvent(event) addReplyInternalToEvent(event);
ipcMainInternal.emit(channel, event, ...args) ipcMainInternal.emit(channel, event, ...args);
} else { } else {
addReplyToEvent(event) addReplyToEvent(event);
this.emit('ipc-message-sync', event, channel, ...args) this.emit('ipc-message-sync', event, channel, ...args);
ipcMain.emit(channel, event, ...args) ipcMain.emit(channel, event, ...args);
} }
}) });
// Handle context menu action request from pepper plugin. // Handle context menu action request from pepper plugin.
this.on('pepper-context-menu', function (event, params, callback) { this.on('pepper-context-menu', function (event, params, callback) {
// Access Menu via electron.Menu to prevent circular require. // Access Menu via electron.Menu to prevent circular require.
const menu = electron.Menu.buildFromTemplate(params.menu) const menu = electron.Menu.buildFromTemplate(params.menu);
menu.popup({ menu.popup({
window: event.sender.getOwnerBrowserWindow(), window: event.sender.getOwnerBrowserWindow(),
x: params.x, x: params.x,
y: params.y, y: params.y,
callback callback
}) });
}) });
this.on('crashed', (event, ...args) => { this.on('crashed', (event, ...args) => {
app.emit('renderer-process-crashed', event, this, ...args) app.emit('renderer-process-crashed', event, this, ...args);
}) });
// The devtools requests the webContents to reload. // The devtools requests the webContents to reload.
this.on('devtools-reload-page', function () { this.on('devtools-reload-page', function () {
this.reload() this.reload();
}) });
// Handle window.open for BrowserWindow and BrowserView. // Handle window.open for BrowserWindow and BrowserView.
if (['browserView', 'window'].includes(this.getType())) { if (['browserView', 'window'].includes(this.getType())) {
@@ -503,9 +503,9 @@ WebContents.prototype._init = function () {
show: true, show: true,
width: 800, width: 800,
height: 600 height: 600
} };
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, postData) internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, postData);
}) });
// Create a new browser window for the native implementation of // Create a new browser window for the native implementation of
// "window.open", used in sandbox and nativeWindowOpen mode. // "window.open", used in sandbox and nativeWindowOpen mode.
@@ -513,8 +513,8 @@ WebContents.prototype._init = function () {
userGesture, left, top, width, height, url, frameName) => { userGesture, left, top, width, height, url, frameName) => {
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' && if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
disposition !== 'background-tab')) { disposition !== 'background-tab')) {
event.preventDefault() event.preventDefault();
return return;
} }
const options = { const options = {
@@ -524,54 +524,74 @@ WebContents.prototype._init = function () {
width: width || 800, width: width || 800,
height: height || 600, height: height || 600,
webContents webContents
} };
const referrer = { url: '', policy: 'default' } const referrer = { url: '', policy: 'default' };
internalWindowOpen(event, url, referrer, frameName, disposition, options) internalWindowOpen(event, url, referrer, frameName, disposition, options);
}) });
} }
this.on('login', (event, ...args) => { this.on('login', (event, ...args) => {
app.emit('login', event, this, ...args) app.emit('login', event, this, ...args);
}) });
const event = process.electronBinding('event').createEmpty() const event = process.electronBinding('event').createEmpty();
app.emit('web-contents-created', event, this) app.emit('web-contents-created', event, this);
}
// Deprecations // Properties
deprecate.fnToProperty(WebContents.prototype, 'audioMuted', '_isAudioMuted', '_setAudioMuted')
deprecate.fnToProperty(WebContents.prototype, 'userAgent', '_getUserAgent', '_setUserAgent') Object.defineProperty(this, 'audioMuted', {
deprecate.fnToProperty(WebContents.prototype, 'zoomLevel', '_getZoomLevel', '_setZoomLevel') get: () => this.isAudioMuted(),
deprecate.fnToProperty(WebContents.prototype, 'zoomFactor', '_getZoomFactor', '_setZoomFactor') set: (muted) => this.setAudioMuted(muted)
deprecate.fnToProperty(WebContents.prototype, 'frameRate', '_getFrameRate', '_setFrameRate') });
Object.defineProperty(this, 'userAgent', {
get: () => this.getUserAgent(),
set: (agent) => this.setUserAgent(agent)
});
Object.defineProperty(this, 'zoomLevel', {
get: () => this.getZoomLevel(),
set: (level) => this.setZoomLevel(level)
});
Object.defineProperty(this, 'zoomFactor', {
get: () => this.getZoomFactor(),
set: (factor) => this.setZoomFactor(factor)
});
Object.defineProperty(this, 'frameRate', {
get: () => this.getFrameRate(),
set: (rate) => this.setFrameRate(rate)
});
};
// JavaScript wrapper of Debugger. // JavaScript wrapper of Debugger.
const { Debugger } = process.electronBinding('debugger') const { Debugger } = process.electronBinding('debugger');
Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype) Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype);
// Public APIs. // Public APIs.
module.exports = { module.exports = {
create (options = {}) { create (options = {}) {
return binding.create(options) return binding.create(options);
}, },
fromId (id) { fromId (id) {
return binding.fromId(id) return binding.fromId(id);
}, },
getFocusedWebContents () { getFocusedWebContents () {
let focused = null let focused = null;
for (const contents of binding.getAllWebContents()) { for (const contents of binding.getAllWebContents()) {
if (!contents.isFocused()) continue if (!contents.isFocused()) continue;
if (focused == null) focused = contents if (focused == null) focused = contents;
// Return webview web contents which may be embedded inside another // Return webview web contents which may be embedded inside another
// web contents that is also reporting as focused // web contents that is also reporting as focused
if (contents.getType() === 'webview') return contents if (contents.getType() === 'webview') return contents;
} }
return focused return focused;
}, },
getAllWebContents () { getAllWebContents () {
return binding.getAllWebContents() return binding.getAllWebContents();
}
} }
};

View File

@@ -1,37 +1,37 @@
'use strict' 'use strict';
// This is a temporary shim to aid in transition from the old // This is a temporary shim to aid in transition from the old
// BrowserWindow-based extensions stuff to the new native-backed extensions // BrowserWindow-based extensions stuff to the new native-backed extensions
// API. // API.
if (!process.electronBinding('features').isExtensionsEnabled()) { if (!process.electronBinding('features').isExtensionsEnabled()) {
throw new Error('Attempted to load JS chrome-extension shim without //extensions support enabled') throw new Error('Attempted to load JS chrome-extension shim without //extensions support enabled');
} }
const { app, session, BrowserWindow, deprecate } = require('electron') const { app, session, BrowserWindow, deprecate } = require('electron');
app.whenReady().then(function () { app.whenReady().then(function () {
const addExtension = function (srcDirectory) { const addExtension = function (srcDirectory) {
return session.defaultSession.loadExtension(srcDirectory) return session.defaultSession.loadExtension(srcDirectory);
} };
const removeExtension = function (name) { const removeExtension = function (name) {
const extension = session.defaultSession.getAllExtensions().find(e => e.name === name) const extension = session.defaultSession.getAllExtensions().find(e => e.name === name);
if (extension) { session.defaultSession.removeExtension(extension.id) } if (extension) { session.defaultSession.removeExtension(extension.id); }
} };
const getExtensions = function () { const getExtensions = function () {
const extensions = {} const extensions = {};
session.defaultSession.getAllExtensions().forEach(e => { session.defaultSession.getAllExtensions().forEach(e => {
extensions[e.name] = e extensions[e.name] = e;
}) });
return extensions return extensions;
} };
BrowserWindow.addExtension = deprecate.moveAPI(addExtension, 'BrowserWindow.addExtension', 'session.loadExtension') BrowserWindow.addExtension = deprecate.moveAPI(addExtension, 'BrowserWindow.addExtension', 'session.loadExtension');
BrowserWindow.removeExtension = deprecate.moveAPI(removeExtension, 'BrowserWindow.removeExtension', 'session.removeExtension') BrowserWindow.removeExtension = deprecate.moveAPI(removeExtension, 'BrowserWindow.removeExtension', 'session.removeExtension');
BrowserWindow.getExtensions = deprecate.moveAPI(getExtensions, 'BrowserWindow.getExtensions', 'session.getAllExtensions') BrowserWindow.getExtensions = deprecate.moveAPI(getExtensions, 'BrowserWindow.getExtensions', 'session.getAllExtensions');
BrowserWindow.addDevToolsExtension = deprecate.moveAPI(addExtension, 'BrowserWindow.addDevToolsExtension', 'session.loadExtension') BrowserWindow.addDevToolsExtension = deprecate.moveAPI(addExtension, 'BrowserWindow.addDevToolsExtension', 'session.loadExtension');
BrowserWindow.removeDevToolsExtension = deprecate.moveAPI(removeExtension, 'BrowserWindow.removeDevToolsExtension', 'session.removeExtension') BrowserWindow.removeDevToolsExtension = deprecate.moveAPI(removeExtension, 'BrowserWindow.removeDevToolsExtension', 'session.removeExtension');
BrowserWindow.getDevToolsExtensions = deprecate.moveAPI(getExtensions, 'BrowserWindow.getDevToolsExtensions', 'session.getAllExtensions') BrowserWindow.getDevToolsExtensions = deprecate.moveAPI(getExtensions, 'BrowserWindow.getDevToolsExtensions', 'session.getAllExtensions');
}) });

View File

@@ -1,105 +1,105 @@
'use strict' 'use strict';
if (process.electronBinding('features').isExtensionsEnabled()) { if (process.electronBinding('features').isExtensionsEnabled()) {
throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled') throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled');
} }
const { app, webContents, BrowserWindow } = require('electron') const { app, webContents, BrowserWindow } = require('electron');
const { getAllWebContents } = process.electronBinding('web_contents') const { getAllWebContents } = process.electronBinding('web_contents');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const { Buffer } = require('buffer') const { Buffer } = require('buffer');
const fs = require('fs') const fs = require('fs');
const path = require('path') const path = require('path');
const url = require('url') const url = require('url');
const util = require('util') const util = require('util');
// Mapping between extensionId(hostname) and manifest. // Mapping between extensionId(hostname) and manifest.
const manifestMap = {} // extensionId => manifest const manifestMap = {}; // extensionId => manifest
const manifestNameMap = {} // name => manifest const manifestNameMap = {}; // name => manifest
const devToolsExtensionNames = new Set() const devToolsExtensionNames = new Set();
const generateExtensionIdFromName = function (name) { const generateExtensionIdFromName = function (name) {
return name.replace(/[\W_]+/g, '-').toLowerCase() return name.replace(/[\W_]+/g, '-').toLowerCase();
} };
const isWindowOrWebView = function (webContents) { const isWindowOrWebView = function (webContents) {
const type = webContents.getType() const type = webContents.getType();
return type === 'window' || type === 'webview' return type === 'window' || type === 'webview';
} };
const isBackgroundPage = function (webContents) { const isBackgroundPage = function (webContents) {
return webContents.getType() === 'backgroundPage' return webContents.getType() === 'backgroundPage';
} };
// Create or get manifest object from |srcDirectory|. // Create or get manifest object from |srcDirectory|.
const getManifestFromPath = function (srcDirectory) { const getManifestFromPath = function (srcDirectory) {
let manifest let manifest;
let manifestContent let manifestContent;
try { try {
manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json')) manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json'));
} catch (readError) { } catch (readError) {
console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`) console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`);
console.warn(readError.stack || readError) console.warn(readError.stack || readError);
throw readError throw readError;
} }
try { try {
manifest = JSON.parse(manifestContent) manifest = JSON.parse(manifestContent);
} catch (parseError) { } catch (parseError) {
console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`) console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`);
console.warn(parseError.stack || parseError) console.warn(parseError.stack || parseError);
throw parseError throw parseError;
} }
if (!manifestNameMap[manifest.name]) { if (!manifestNameMap[manifest.name]) {
const extensionId = generateExtensionIdFromName(manifest.name) const extensionId = generateExtensionIdFromName(manifest.name);
manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest;
let extensionURL = url.format({ let extensionURL = url.format({
protocol: 'chrome-extension', protocol: 'chrome-extension',
slashes: true, slashes: true,
hostname: extensionId, hostname: extensionId,
pathname: manifest.devtools_page pathname: manifest.devtools_page
}) });
// Chromium requires that startPage matches '([^:]+:\/\/[^/]*)\/' // Chromium requires that startPage matches '([^:]+:\/\/[^/]*)\/'
// We also can't use the file:// protocol here since that would make Chromium // We also can't use the file:// protocol here since that would make Chromium
// treat all extension resources as being relative to root which we don't want. // treat all extension resources as being relative to root which we don't want.
if (!manifest.devtools_page) extensionURL += '/' if (!manifest.devtools_page) extensionURL += '/';
Object.assign(manifest, { Object.assign(manifest, {
srcDirectory: srcDirectory, srcDirectory: srcDirectory,
extensionId: extensionId, extensionId: extensionId,
startPage: extensionURL startPage: extensionURL
}) });
return manifest return manifest;
} else if (manifest && manifest.name) { } else if (manifest && manifest.name) {
console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`) console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`);
return manifest return manifest;
}
} }
};
// Manage the background pages. // Manage the background pages.
const backgroundPages = {} const backgroundPages = {};
const startBackgroundPages = function (manifest) { const startBackgroundPages = function (manifest) {
if (backgroundPages[manifest.extensionId] || !manifest.background) return if (backgroundPages[manifest.extensionId] || !manifest.background) return;
let html let html;
let name let name;
if (manifest.background.page) { if (manifest.background.page) {
name = manifest.background.page name = manifest.background.page;
html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page)) html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page));
} else { } else {
name = '_generated_background_page.html' name = '_generated_background_page.html';
const scripts = manifest.background.scripts.map((name) => { const scripts = manifest.background.scripts.map((name) => {
return `<script src="${name}"></script>` return `<script src="${name}"></script>`;
}).join('') }).join('');
html = Buffer.from(`<html><body>${scripts}</body></html>`) html = Buffer.from(`<html><body>${scripts}</body></html>`);
} }
const contents = webContents.create({ const contents = webContents.create({
@@ -107,36 +107,36 @@ const startBackgroundPages = function (manifest) {
type: 'backgroundPage', type: 'backgroundPage',
sandbox: true, sandbox: true,
enableRemoteModule: false enableRemoteModule: false
}) });
backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name } backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name };
contents.loadURL(url.format({ contents.loadURL(url.format({
protocol: 'chrome-extension', protocol: 'chrome-extension',
slashes: true, slashes: true,
hostname: manifest.extensionId, hostname: manifest.extensionId,
pathname: name pathname: name
})) }));
} };
const removeBackgroundPages = function (manifest) { const removeBackgroundPages = function (manifest) {
if (!backgroundPages[manifest.extensionId]) return if (!backgroundPages[manifest.extensionId]) return;
backgroundPages[manifest.extensionId].webContents.destroy() backgroundPages[manifest.extensionId].webContents.destroy();
delete backgroundPages[manifest.extensionId] delete backgroundPages[manifest.extensionId];
} };
const sendToBackgroundPages = function (...args) { const sendToBackgroundPages = function (...args) {
for (const page of Object.values(backgroundPages)) { for (const page of Object.values(backgroundPages)) {
if (!page.webContents.isDestroyed()) { if (!page.webContents.isDestroyed()) {
page.webContents._sendInternalToAll(...args) page.webContents._sendInternalToAll(...args);
}
} }
} }
};
// Dispatch web contents events to Chrome APIs // Dispatch web contents events to Chrome APIs
const hookWebContentsEvents = function (webContents) { const hookWebContentsEvents = function (webContents) {
const tabId = webContents.id const tabId = webContents.id;
sendToBackgroundPages('CHROME_TABS_ONCREATED') sendToBackgroundPages('CHROME_TABS_ONCREATED');
webContents.on('will-navigate', (event, url) => { webContents.on('will-navigate', (event, url) => {
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', { sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', {
@@ -146,8 +146,8 @@ const hookWebContentsEvents = function (webContents) {
tabId: tabId, tabId: tabId,
timeStamp: Date.now(), timeStamp: Date.now(),
url: url url: url
}) });
}) });
webContents.on('did-navigate', (event, url) => { webContents.on('did-navigate', (event, url) => {
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', { sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', {
@@ -157,189 +157,189 @@ const hookWebContentsEvents = function (webContents) {
tabId: tabId, tabId: tabId,
timeStamp: Date.now(), timeStamp: Date.now(),
url: url url: url
}) });
}) });
webContents.once('destroyed', () => { webContents.once('destroyed', () => {
sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId) sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId);
}) });
} };
// Handle the chrome.* API messages. // Handle the chrome.* API messages.
let nextId = 0 let nextId = 0;
ipcMainUtils.handleSync('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) { ipcMainUtils.handleSync('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) {
if (isBackgroundPage(event.sender)) { if (isBackgroundPage(event.sender)) {
throw new Error('chrome.runtime.connect is not supported in background page') throw new Error('chrome.runtime.connect is not supported in background page');
} }
const page = backgroundPages[extensionId] const page = backgroundPages[extensionId];
if (!page || page.webContents.isDestroyed()) { if (!page || page.webContents.isDestroyed()) {
throw new Error(`Connect to unknown extension ${extensionId}`) throw new Error(`Connect to unknown extension ${extensionId}`);
} }
const tabId = page.webContents.id const tabId = page.webContents.id;
const portId = ++nextId const portId = ++nextId;
event.sender.once('render-view-deleted', () => { event.sender.once('render-view-deleted', () => {
if (page.webContents.isDestroyed()) return if (page.webContents.isDestroyed()) return;
page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`) page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`);
}) });
page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo) page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo);
return { tabId, portId } return { tabId, portId };
}) });
ipcMainUtils.handleSync('CHROME_EXTENSION_MANIFEST', function (event, extensionId) { ipcMainUtils.handleSync('CHROME_EXTENSION_MANIFEST', function (event, extensionId) {
const manifest = manifestMap[extensionId] const manifest = manifestMap[extensionId];
if (!manifest) { if (!manifest) {
throw new Error(`Invalid extensionId: ${extensionId}`) throw new Error(`Invalid extensionId: ${extensionId}`);
} }
return manifest return manifest;
}) });
ipcMainInternal.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extensionId, message) { ipcMainInternal.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extensionId, message) {
if (isBackgroundPage(event.sender)) { if (isBackgroundPage(event.sender)) {
throw new Error('chrome.runtime.sendMessage is not supported in background page') throw new Error('chrome.runtime.sendMessage is not supported in background page');
} }
const page = backgroundPages[extensionId] const page = backgroundPages[extensionId];
if (!page || page.webContents.isDestroyed()) { if (!page || page.webContents.isDestroyed()) {
throw new Error(`Connect to unknown extension ${extensionId}`) throw new Error(`Connect to unknown extension ${extensionId}`);
} }
return ipcMainUtils.invokeInWebContents(page.webContents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message) return ipcMainUtils.invokeInWebContents(page.webContents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message);
}) });
ipcMainInternal.handle('CHROME_TABS_SEND_MESSAGE', async function (event, tabId, extensionId, message) { ipcMainInternal.handle('CHROME_TABS_SEND_MESSAGE', async function (event, tabId, extensionId, message) {
const contents = webContents.fromId(tabId) const contents = webContents.fromId(tabId);
if (!contents) { if (!contents) {
throw new Error(`Sending message to unknown tab ${tabId}`) throw new Error(`Sending message to unknown tab ${tabId}`);
} }
const senderTabId = isBackgroundPage(event.sender) ? null : event.sender.id const senderTabId = isBackgroundPage(event.sender) ? null : event.sender.id;
return ipcMainUtils.invokeInWebContents(contents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message) return ipcMainUtils.invokeInWebContents(contents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message);
}) });
const getLanguage = () => { const getLanguage = () => {
return app.getLocale().replace(/-.*$/, '').toLowerCase() return app.getLocale().replace(/-.*$/, '').toLowerCase();
} };
const getMessagesPath = (extensionId) => { const getMessagesPath = (extensionId) => {
const metadata = manifestMap[extensionId] const metadata = manifestMap[extensionId];
if (!metadata) { if (!metadata) {
throw new Error(`Invalid extensionId: ${extensionId}`) throw new Error(`Invalid extensionId: ${extensionId}`);
} }
const localesDirectory = path.join(metadata.srcDirectory, '_locales') const localesDirectory = path.join(metadata.srcDirectory, '_locales');
const language = getLanguage() const language = getLanguage();
try { try {
const filename = path.join(localesDirectory, language, 'messages.json') const filename = path.join(localesDirectory, language, 'messages.json');
fs.accessSync(filename, fs.constants.R_OK) fs.accessSync(filename, fs.constants.R_OK);
return filename return filename;
} catch { } catch {
const defaultLocale = metadata.default_locale || 'en' const defaultLocale = metadata.default_locale || 'en';
return path.join(localesDirectory, defaultLocale, 'messages.json') return path.join(localesDirectory, defaultLocale, 'messages.json');
}
} }
};
ipcMainUtils.handleSync('CHROME_GET_MESSAGES', async function (event, extensionId) { ipcMainUtils.handleSync('CHROME_GET_MESSAGES', async function (event, extensionId) {
const messagesPath = getMessagesPath(extensionId) const messagesPath = getMessagesPath(extensionId);
return fs.promises.readFile(messagesPath, 'utf8') return fs.promises.readFile(messagesPath, 'utf8');
}) });
const validStorageTypes = new Set(['sync', 'local']) const validStorageTypes = new Set(['sync', 'local']);
const getChromeStoragePath = (storageType, extensionId) => { const getChromeStoragePath = (storageType, extensionId) => {
if (!validStorageTypes.has(storageType)) { if (!validStorageTypes.has(storageType)) {
throw new Error(`Invalid storageType: ${storageType}`) throw new Error(`Invalid storageType: ${storageType}`);
} }
if (!manifestMap[extensionId]) { if (!manifestMap[extensionId]) {
throw new Error(`Invalid extensionId: ${extensionId}`) throw new Error(`Invalid extensionId: ${extensionId}`);
} }
return path.join(app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`) return path.join(app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`);
} };
ipcMainInternal.handle('CHROME_STORAGE_READ', async function (event, storageType, extensionId) { ipcMainInternal.handle('CHROME_STORAGE_READ', async function (event, storageType, extensionId) {
const filePath = getChromeStoragePath(storageType, extensionId) const filePath = getChromeStoragePath(storageType, extensionId);
try { try {
return await fs.promises.readFile(filePath, 'utf8') return await fs.promises.readFile(filePath, 'utf8');
} catch (error) { } catch (error) {
if (error.code === 'ENOENT') { if (error.code === 'ENOENT') {
return null return null;
} else { } else {
throw error throw error;
} }
} }
}) });
ipcMainInternal.handle('CHROME_STORAGE_WRITE', async function (event, storageType, extensionId, data) { ipcMainInternal.handle('CHROME_STORAGE_WRITE', async function (event, storageType, extensionId, data) {
const filePath = getChromeStoragePath(storageType, extensionId) const filePath = getChromeStoragePath(storageType, extensionId);
try { try {
await fs.promises.mkdir(path.dirname(filePath), { recursive: true }) await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
} catch { } catch {
// we just ignore the errors of mkdir // we just ignore the errors of mkdir
} }
return fs.promises.writeFile(filePath, data, 'utf8') return fs.promises.writeFile(filePath, data, 'utf8');
}) });
const isChromeExtension = function (pageURL) { const isChromeExtension = function (pageURL) {
const { protocol } = url.parse(pageURL) const { protocol } = url.parse(pageURL);
return protocol === 'chrome-extension:' return protocol === 'chrome-extension:';
} };
const assertChromeExtension = function (contents, api) { const assertChromeExtension = function (contents, api) {
const pageURL = contents._getURL() const pageURL = contents._getURL();
if (!isChromeExtension(pageURL)) { if (!isChromeExtension(pageURL)) {
console.error(`Blocked ${pageURL} from calling ${api}`) console.error(`Blocked ${pageURL} from calling ${api}`);
throw new Error(`Blocked ${api}`) throw new Error(`Blocked ${api}`);
}
} }
};
ipcMainInternal.handle('CHROME_TABS_EXECUTE_SCRIPT', async function (event, tabId, extensionId, details) { ipcMainInternal.handle('CHROME_TABS_EXECUTE_SCRIPT', async function (event, tabId, extensionId, details) {
assertChromeExtension(event.sender, 'chrome.tabs.executeScript()') assertChromeExtension(event.sender, 'chrome.tabs.executeScript()');
const contents = webContents.fromId(tabId) const contents = webContents.fromId(tabId);
if (!contents) { if (!contents) {
throw new Error(`Sending message to unknown tab ${tabId}`) throw new Error(`Sending message to unknown tab ${tabId}`);
} }
let code, url let code, url;
if (details.file) { if (details.file) {
const manifest = manifestMap[extensionId] const manifest = manifestMap[extensionId];
code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file))) code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)));
url = `chrome-extension://${extensionId}${details.file}` url = `chrome-extension://${extensionId}${details.file}`;
} else { } else {
code = details.code code = details.code;
url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js` url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`;
} }
return ipcMainUtils.invokeInWebContents(contents, false, 'CHROME_TABS_EXECUTE_SCRIPT', extensionId, url, code) return ipcMainUtils.invokeInWebContents(contents, false, 'CHROME_TABS_EXECUTE_SCRIPT', extensionId, url, code);
}) });
exports.getContentScripts = () => { exports.getContentScripts = () => {
return Object.values(contentScripts) return Object.values(contentScripts);
} };
// Transfer the content scripts to renderer. // Transfer the content scripts to renderer.
const contentScripts = {} const contentScripts = {};
const injectContentScripts = function (manifest) { const injectContentScripts = function (manifest) {
if (contentScripts[manifest.name] || !manifest.content_scripts) return if (contentScripts[manifest.name] || !manifest.content_scripts) return;
const readArrayOfFiles = function (relativePath) { const readArrayOfFiles = function (relativePath) {
return { return {
url: `chrome-extension://${manifest.extensionId}/${relativePath}`, url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath))) code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
} };
} };
const contentScriptToEntry = function (script) { const contentScriptToEntry = function (script) {
return { return {
@@ -348,25 +348,25 @@ const injectContentScripts = function (manifest) {
css: script.css ? script.css.map(readArrayOfFiles) : [], css: script.css ? script.css.map(readArrayOfFiles) : [],
runAt: script.run_at || 'document_idle', runAt: script.run_at || 'document_idle',
allFrames: script.all_frames || false allFrames: script.all_frames || false
} };
} };
try { try {
const entry = { const entry = {
extensionId: manifest.extensionId, extensionId: manifest.extensionId,
contentScripts: manifest.content_scripts.map(contentScriptToEntry) contentScripts: manifest.content_scripts.map(contentScriptToEntry)
} };
contentScripts[manifest.name] = entry contentScripts[manifest.name] = entry;
} catch (e) { } catch (e) {
console.error('Failed to read content scripts', e) console.error('Failed to read content scripts', e);
}
} }
};
const removeContentScripts = function (manifest) { const removeContentScripts = function (manifest) {
if (!contentScripts[manifest.name]) return if (!contentScripts[manifest.name]) return;
delete contentScripts[manifest.name] delete contentScripts[manifest.name];
} };
// Transfer the |manifest| to a format that can be recognized by the // Transfer the |manifest| to a format that can be recognized by the
// |DevToolsAPI.addExtensions|. // |DevToolsAPI.addExtensions|.
@@ -376,167 +376,167 @@ const manifestToExtensionInfo = function (manifest) {
srcDirectory: manifest.srcDirectory, srcDirectory: manifest.srcDirectory,
name: manifest.name, name: manifest.name,
exposeExperimentalAPIs: true exposeExperimentalAPIs: true
} };
} };
// Load the extensions for the window. // Load the extensions for the window.
const loadExtension = function (manifest) { const loadExtension = function (manifest) {
startBackgroundPages(manifest) startBackgroundPages(manifest);
injectContentScripts(manifest) injectContentScripts(manifest);
} };
const loadDevToolsExtensions = function (win, manifests) { const loadDevToolsExtensions = function (win, manifests) {
if (!win.devToolsWebContents) return if (!win.devToolsWebContents) return;
manifests.forEach(loadExtension) manifests.forEach(loadExtension);
const extensionInfoArray = manifests.map(manifestToExtensionInfo) const extensionInfoArray = manifests.map(manifestToExtensionInfo);
extensionInfoArray.forEach((extension) => { extensionInfoArray.forEach((extension) => {
win.devToolsWebContents._grantOriginAccess(extension.startPage) win.devToolsWebContents._grantOriginAccess(extension.startPage);
}) });
extensionInfoArray.forEach((extensionInfo) => { extensionInfoArray.forEach((extensionInfo) => {
const info = JSON.stringify(extensionInfo) const info = JSON.stringify(extensionInfo);
win.devToolsWebContents.executeJavaScript(`Extensions.extensionServer._addExtension(${info})`) win.devToolsWebContents.executeJavaScript(`Extensions.extensionServer._addExtension(${info})`);
}) });
} };
app.on('web-contents-created', function (event, webContents) { app.on('web-contents-created', function (event, webContents) {
if (!isWindowOrWebView(webContents)) return if (!isWindowOrWebView(webContents)) return;
hookWebContentsEvents(webContents) hookWebContentsEvents(webContents);
webContents.on('devtools-opened', function () { webContents.on('devtools-opened', function () {
loadDevToolsExtensions(webContents, Object.values(manifestMap)) loadDevToolsExtensions(webContents, Object.values(manifestMap));
}) });
}) });
// The chrome-extension: can map a extension URL request to real file path. // The chrome-extension: can map a extension URL request to real file path.
const chromeExtensionHandler = function (request, callback) { const chromeExtensionHandler = function (request, callback) {
const parsed = url.parse(request.url) const parsed = url.parse(request.url);
if (!parsed.hostname || !parsed.path) return callback() if (!parsed.hostname || !parsed.path) return callback();
const manifest = manifestMap[parsed.hostname] const manifest = manifestMap[parsed.hostname];
if (!manifest) return callback() if (!manifest) return callback();
const page = backgroundPages[parsed.hostname] const page = backgroundPages[parsed.hostname];
if (page && parsed.path === `/${page.name}`) { if (page && parsed.path === `/${page.name}`) {
// Disabled due to false positive in StandardJS // Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback({ return callback({
mimeType: 'text/html', mimeType: 'text/html',
data: page.html data: page.html
}) });
} }
fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) { fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
if (err) { if (err) {
// Disabled due to false positive in StandardJS // Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal // eslint-disable-next-line standard/no-callback-literal
return callback(-6) // FILE_NOT_FOUND return callback(-6); // FILE_NOT_FOUND
} else { } else {
return callback(content) return callback(content);
}
})
} }
});
};
app.on('session-created', function (ses) { app.on('session-created', function (ses) {
ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler) ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler);
}) });
// The persistent path of "DevTools Extensions" preference file. // The persistent path of "DevTools Extensions" preference file.
let loadedDevToolsExtensionsPath = null let loadedDevToolsExtensionsPath = null;
app.on('will-quit', function () { app.on('will-quit', function () {
try { try {
const loadedDevToolsExtensions = Array.from(devToolsExtensionNames) const loadedDevToolsExtensions = Array.from(devToolsExtensionNames)
.map(name => manifestNameMap[name].srcDirectory) .map(name => manifestNameMap[name].srcDirectory);
if (loadedDevToolsExtensions.length > 0) { if (loadedDevToolsExtensions.length > 0) {
try { try {
fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath)) fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath));
} catch { } catch {
// Ignore error // Ignore error
} }
fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions)) fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions));
} else { } else {
fs.unlinkSync(loadedDevToolsExtensionsPath) fs.unlinkSync(loadedDevToolsExtensionsPath);
} }
} catch { } catch {
// Ignore error // Ignore error
} }
}) });
// We can not use protocol or BrowserWindow until app is ready. // We can not use protocol or BrowserWindow until app is ready.
app.whenReady().then(function () { app.whenReady().then(function () {
// The public API to add/remove extensions. // The public API to add/remove extensions.
BrowserWindow.addExtension = function (srcDirectory) { BrowserWindow.addExtension = function (srcDirectory) {
const manifest = getManifestFromPath(srcDirectory) const manifest = getManifestFromPath(srcDirectory);
if (manifest) { if (manifest) {
loadExtension(manifest) loadExtension(manifest);
for (const webContents of getAllWebContents()) { for (const webContents of getAllWebContents()) {
if (isWindowOrWebView(webContents)) { if (isWindowOrWebView(webContents)) {
loadDevToolsExtensions(webContents, [manifest]) loadDevToolsExtensions(webContents, [manifest]);
} }
} }
return manifest.name return manifest.name;
}
} }
};
BrowserWindow.removeExtension = function (name) { BrowserWindow.removeExtension = function (name) {
const manifest = manifestNameMap[name] const manifest = manifestNameMap[name];
if (!manifest) return if (!manifest) return;
removeBackgroundPages(manifest) removeBackgroundPages(manifest);
removeContentScripts(manifest) removeContentScripts(manifest);
delete manifestMap[manifest.extensionId] delete manifestMap[manifest.extensionId];
delete manifestNameMap[name] delete manifestNameMap[name];
} };
BrowserWindow.getExtensions = function () { BrowserWindow.getExtensions = function () {
const extensions = {} const extensions = {};
Object.keys(manifestNameMap).forEach(function (name) { Object.keys(manifestNameMap).forEach(function (name) {
const manifest = manifestNameMap[name] const manifest = manifestNameMap[name];
extensions[name] = { name: manifest.name, version: manifest.version } extensions[name] = { name: manifest.name, version: manifest.version };
}) });
return extensions return extensions;
} };
BrowserWindow.addDevToolsExtension = function (srcDirectory) { BrowserWindow.addDevToolsExtension = function (srcDirectory) {
const manifestName = BrowserWindow.addExtension(srcDirectory) const manifestName = BrowserWindow.addExtension(srcDirectory);
if (manifestName) { if (manifestName) {
devToolsExtensionNames.add(manifestName) devToolsExtensionNames.add(manifestName);
}
return manifestName
} }
return manifestName;
};
BrowserWindow.removeDevToolsExtension = function (name) { BrowserWindow.removeDevToolsExtension = function (name) {
BrowserWindow.removeExtension(name) BrowserWindow.removeExtension(name);
devToolsExtensionNames.delete(name) devToolsExtensionNames.delete(name);
} };
BrowserWindow.getDevToolsExtensions = function () { BrowserWindow.getDevToolsExtensions = function () {
const extensions = BrowserWindow.getExtensions() const extensions = BrowserWindow.getExtensions();
const devExtensions = {} const devExtensions = {};
Array.from(devToolsExtensionNames).forEach(function (name) { Array.from(devToolsExtensionNames).forEach(function (name) {
if (!extensions[name]) return if (!extensions[name]) return;
devExtensions[name] = extensions[name] devExtensions[name] = extensions[name];
}) });
return devExtensions return devExtensions;
} };
// Load persisted extensions. // Load persisted extensions.
loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions') loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions');
try { try {
const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath)) const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath));
if (Array.isArray(loadedDevToolsExtensions)) { if (Array.isArray(loadedDevToolsExtensions)) {
for (const srcDirectory of loadedDevToolsExtensions) { for (const srcDirectory of loadedDevToolsExtensions) {
// Start background pages and set content scripts. // Start background pages and set content scripts.
BrowserWindow.addDevToolsExtension(srcDirectory) BrowserWindow.addDevToolsExtension(srcDirectory);
} }
} }
} catch (error) { } catch (error) {
if (process.env.ELECTRON_ENABLE_LOGGING && error.code !== 'ENOENT') { if (process.env.ELECTRON_ENABLE_LOGGING && error.code !== 'ENOENT') {
console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath) console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath);
console.error(error) console.error(error);
} }
} }
}) });

View File

@@ -1,25 +1,25 @@
'use strict' 'use strict';
const { app } = require('electron') const { app } = require('electron');
const path = require('path') const path = require('path');
const getTempDirectory = function () { const getTempDirectory = function () {
try { try {
return app.getPath('temp') return app.getPath('temp');
} catch { } catch {
// Delibrately laze-load the os module, this file is on the hot // Delibrately laze-load the os module, this file is on the hot
// path when booting Electron and os takes between 5 - 8ms to load and we do not need it yet // path when booting Electron and os takes between 5 - 8ms to load and we do not need it yet
return require('os').tmpdir() return require('os').tmpdir();
}
} }
};
exports.crashReporterInit = function (options) { exports.crashReporterInit = function (options) {
const productName = options.productName || app.name const productName = options.productName || app.name;
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`) const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`);
return { return {
productName, productName,
crashesDirectory, crashesDirectory,
appVersion: app.getVersion() appVersion: app.getVersion()
} };
} };

View File

@@ -1,11 +1,11 @@
import { shell, Menu } from 'electron' import { shell, Menu } from 'electron';
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const isMac = process.platform === 'darwin' const isMac = process.platform === 'darwin';
export const setDefaultApplicationMenu = () => { export const setDefaultApplicationMenu = () => {
if (v8Util.getHiddenValue<boolean>(global, 'applicationMenuSet')) return if (v8Util.getHiddenValue<boolean>(global, 'applicationMenuSet')) return;
const helpMenu: Electron.MenuItemConstructorOptions = { const helpMenu: Electron.MenuItemConstructorOptions = {
role: 'help', role: 'help',
@@ -13,32 +13,32 @@ export const setDefaultApplicationMenu = () => {
{ {
label: 'Learn More', label: 'Learn More',
click: async () => { click: async () => {
await shell.openExternal('https://electronjs.org') await shell.openExternal('https://electronjs.org');
} }
}, },
{ {
label: 'Documentation', label: 'Documentation',
click: async () => { click: async () => {
const version = process.versions.electron const version = process.versions.electron;
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`) await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
} }
}, },
{ {
label: 'Community Discussions', label: 'Community Discussions',
click: async () => { click: async () => {
await shell.openExternal('https://discuss.atom.io/c/electron') await shell.openExternal('https://discuss.atom.io/c/electron');
} }
}, },
{ {
label: 'Search Issues', label: 'Search Issues',
click: async () => { click: async () => {
await shell.openExternal('https://github.com/electron/electron/issues') await shell.openExternal('https://github.com/electron/electron/issues');
} }
} }
] ]
} };
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' } const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
const template: Electron.MenuItemConstructorOptions[] = [ const template: Electron.MenuItemConstructorOptions[] = [
...(isMac ? [macAppMenu] : []), ...(isMac ? [macAppMenu] : []),
{ role: 'fileMenu' }, { role: 'fileMenu' },
@@ -46,8 +46,8 @@ export const setDefaultApplicationMenu = () => {
{ role: 'viewMenu' }, { role: 'viewMenu' },
{ role: 'windowMenu' }, { role: 'windowMenu' },
helpMenu helpMenu
] ];
const menu = Menu.buildFromTemplate(template) const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu) Menu.setApplicationMenu(menu);
} };

View File

@@ -1,66 +1,66 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
const { createDesktopCapturer } = process.electronBinding('desktop_capturer') const { createDesktopCapturer } = process.electronBinding('desktop_capturer');
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b) const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
let currentlyRunning: { let currentlyRunning: {
options: ElectronInternal.GetSourcesOptions; options: ElectronInternal.GetSourcesOptions;
getSources: Promise<ElectronInternal.GetSourcesResult[]>; getSources: Promise<ElectronInternal.GetSourcesResult[]>;
}[] = [] }[] = [];
export const getSources = (event: Electron.IpcMainEvent, options: ElectronInternal.GetSourcesOptions) => { export const getSources = (event: Electron.IpcMainEvent, options: ElectronInternal.GetSourcesOptions) => {
for (const running of currentlyRunning) { for (const running of currentlyRunning) {
if (deepEqual(running.options, options)) { if (deepEqual(running.options, options)) {
// If a request is currently running for the same options // If a request is currently running for the same options
// return that promise // return that promise
return running.getSources return running.getSources;
} }
} }
const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => { const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => {
let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer() let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer();
const stopRunning = () => { const stopRunning = () => {
if (capturer) { if (capturer) {
capturer.emit = null capturer.emit = null;
capturer = null capturer = null;
} }
// Remove from currentlyRunning once we resolve or reject // Remove from currentlyRunning once we resolve or reject
currentlyRunning = currentlyRunning.filter(running => running.options !== options) currentlyRunning = currentlyRunning.filter(running => running.options !== options);
} };
const emitter = new EventEmitter() const emitter = new EventEmitter();
emitter.once('error', (event, error: string) => { emitter.once('error', (event, error: string) => {
stopRunning() stopRunning();
reject(error) reject(error);
}) });
emitter.once('finished', (event, sources: Electron.DesktopCapturerSource[], fetchWindowIcons: boolean) => { emitter.once('finished', (event, sources: Electron.DesktopCapturerSource[], fetchWindowIcons: boolean) => {
stopRunning() stopRunning();
resolve(sources.map(source => ({ resolve(sources.map(source => ({
id: source.id, id: source.id,
name: source.name, name: source.name,
thumbnail: source.thumbnail.toDataURL(), thumbnail: source.thumbnail.toDataURL(),
display_id: source.display_id, display_id: source.display_id,
appIcon: (fetchWindowIcons && source.appIcon) ? source.appIcon.toDataURL() : null appIcon: (fetchWindowIcons && source.appIcon) ? source.appIcon.toDataURL() : null
}))) })));
}) });
capturer.emit = emitter.emit.bind(emitter) capturer.emit = emitter.emit.bind(emitter);
capturer.startHandling(options.captureWindow, options.captureScreen, options.thumbnailSize, options.fetchWindowIcons) capturer.startHandling(options.captureWindow, options.captureScreen, options.thumbnailSize, options.fetchWindowIcons);
// If the WebContents is destroyed before receiving result, just remove the // If the WebContents is destroyed before receiving result, just remove the
// reference to emit and the capturer itself so that it never dispatches // reference to emit and the capturer itself so that it never dispatches
// back to the renderer // back to the renderer
event.sender.once('destroyed', () => stopRunning()) event.sender.once('destroyed', () => stopRunning());
}) });
currentlyRunning.push({ currentlyRunning.push({
options, options,
getSources getSources
}) });
return getSources return getSources;
} };

View File

@@ -1,9 +1,9 @@
import { dialog, Menu } from 'electron' import { dialog, Menu } from 'electron';
import * as fs from 'fs' import * as fs from 'fs';
import * as url from 'url' import * as url from 'url';
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal' import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils' import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) { const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) {
return items.map(function (item) { return items.map(function (item) {
@@ -23,15 +23,15 @@ const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id:
type: 'normal', type: 'normal',
label: item.label, label: item.label,
enabled: item.enabled enabled: item.enabled
} };
if (item.id != null) { if (item.id != null) {
transformed.click = () => handler(item.id) transformed.click = () => handler(item.id);
} }
return transformed return transformed;
}) });
} };
const getEditMenuItems = function (): Electron.MenuItemConstructorOptions[] { const getEditMenuItems = function (): Electron.MenuItemConstructorOptions[] {
return [ return [
@@ -44,56 +44,56 @@ const getEditMenuItems = function (): Electron.MenuItemConstructorOptions[] {
{ role: 'pasteAndMatchStyle' }, { role: 'pasteAndMatchStyle' },
{ role: 'delete' }, { role: 'delete' },
{ role: 'selectAll' } { role: 'selectAll' }
] ];
} };
const isChromeDevTools = function (pageURL: string) { const isChromeDevTools = function (pageURL: string) {
const { protocol } = url.parse(pageURL) const { protocol } = url.parse(pageURL);
return protocol === 'devtools:' return protocol === 'devtools:';
} };
const assertChromeDevTools = function (contents: Electron.WebContents, api: string) { const assertChromeDevTools = function (contents: Electron.WebContents, api: string) {
const pageURL = contents._getURL() const pageURL = contents._getURL();
if (!isChromeDevTools(pageURL)) { if (!isChromeDevTools(pageURL)) {
console.error(`Blocked ${pageURL} from calling ${api}`) console.error(`Blocked ${pageURL} from calling ${api}`);
throw new Error(`Blocked ${api}`) throw new Error(`Blocked ${api}`);
}
} }
};
ipcMainInternal.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event: Electron.IpcMainInvokeEvent, items: ContextMenuItem[], isEditMenu: boolean) { ipcMainInternal.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event: Electron.IpcMainInvokeEvent, items: ContextMenuItem[], isEditMenu: boolean) {
return new Promise(resolve => { return new Promise(resolve => {
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()') assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()');
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve) const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
const menu = Menu.buildFromTemplate(template) const menu = Menu.buildFromTemplate(template);
const window = event.sender.getOwnerBrowserWindow() const window = event.sender.getOwnerBrowserWindow();
menu.popup({ window, callback: () => resolve() }) menu.popup({ window, callback: () => resolve() });
}) });
}) });
ipcMainInternal.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event: Electron.IpcMainInvokeEvent) { ipcMainInternal.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event: Electron.IpcMainInvokeEvent) {
assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()') assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()');
const result = await dialog.showOpenDialog({}) const result = await dialog.showOpenDialog({});
if (result.canceled) return [] if (result.canceled) return [];
const path = result.filePaths[0] const path = result.filePaths[0];
const data = await fs.promises.readFile(path) const data = await fs.promises.readFile(path);
return [path, data] return [path, data];
}) });
ipcMainUtils.handleSync('ELECTRON_INSPECTOR_CONFIRM', async function (event: Electron.IpcMainInvokeEvent, message: string = '', title: string = '') { ipcMainUtils.handleSync('ELECTRON_INSPECTOR_CONFIRM', async function (event: Electron.IpcMainInvokeEvent, message: string = '', title: string = '') {
assertChromeDevTools(event.sender, 'window.confirm()') assertChromeDevTools(event.sender, 'window.confirm()');
const options = { const options = {
message: String(message), message: String(message),
title: String(title), title: String(title),
buttons: ['OK', 'Cancel'], buttons: ['OK', 'Cancel'],
cancelId: 1 cancelId: 1
} };
const window = event.sender.getOwnerBrowserWindow() const window = event.sender.getOwnerBrowserWindow();
const { response } = await dialog.showMessageBox(window, options) const { response } = await dialog.showMessageBox(window, options);
return response === 0 return response === 0;
}) });

View File

@@ -1,14 +1,14 @@
'use strict' 'use strict';
const { webContents } = require('electron') const { webContents } = require('electron');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const parseFeaturesString = require('@electron/internal/common/parse-features-string') const parseFeaturesString = require('@electron/internal/common/parse-features-string');
const { syncMethods, asyncMethods } = require('@electron/internal/common/web-view-methods') const { syncMethods, asyncMethods, properties } = require('@electron/internal/common/web-view-methods');
const { serialize } = require('@electron/internal/common/type-utils') const { serialize } = require('@electron/internal/common/type-utils');
// Doesn't exist in early initialization. // Doesn't exist in early initialization.
let webViewManager = null let webViewManager = null;
const supportedWebViewEvents = [ const supportedWebViewEvents = [
'load-commit', 'load-commit',
@@ -43,155 +43,155 @@ const supportedWebViewEvents = [
'found-in-page', 'found-in-page',
'did-change-theme-color', 'did-change-theme-color',
'update-target-url' 'update-target-url'
] ];
const guestInstances = {} const guestInstances = {};
const embedderElementsMap = {} const embedderElementsMap = {};
function sanitizeOptionsForGuest (options) { function sanitizeOptionsForGuest (options) {
const ret = { ...options } const ret = { ...options };
// WebContents values can't be sent over IPC. // WebContents values can't be sent over IPC.
delete ret.webContents delete ret.webContents;
return ret return ret;
} }
// Create a new guest instance. // Create a new guest instance.
const createGuest = function (embedder, params) { const createGuest = function (embedder, params) {
if (webViewManager == null) { if (webViewManager == null) {
webViewManager = process.electronBinding('web_view_manager') webViewManager = process.electronBinding('web_view_manager');
} }
const guest = webContents.create({ const guest = webContents.create({
type: 'webview', type: 'webview',
partition: params.partition, partition: params.partition,
embedder: embedder embedder: embedder
}) });
const guestInstanceId = guest.id const guestInstanceId = guest.id;
guestInstances[guestInstanceId] = { guestInstances[guestInstanceId] = {
guest: guest, guest: guest,
embedder: embedder embedder: embedder
} };
// Clear the guest from map when it is destroyed. // Clear the guest from map when it is destroyed.
guest.once('destroyed', () => { guest.once('destroyed', () => {
if (guestInstanceId in guestInstances) { if (Object.prototype.hasOwnProperty.call(guestInstances, guestInstanceId)) {
detachGuest(embedder, guestInstanceId) detachGuest(embedder, guestInstanceId);
} }
}) });
// Init guest web view after attached. // Init guest web view after attached.
guest.once('did-attach', function (event) { guest.once('did-attach', function (event) {
params = this.attachParams params = this.attachParams;
delete this.attachParams delete this.attachParams;
const previouslyAttached = this.viewInstanceId != null const previouslyAttached = this.viewInstanceId != null;
this.viewInstanceId = params.instanceId this.viewInstanceId = params.instanceId;
// Only load URL and set size on first attach // Only load URL and set size on first attach
if (previouslyAttached) { if (previouslyAttached) {
return return;
} }
if (params.src) { if (params.src) {
const opts = {} const opts = {};
if (params.httpreferrer) { if (params.httpreferrer) {
opts.httpReferrer = params.httpreferrer opts.httpReferrer = params.httpreferrer;
} }
if (params.useragent) { if (params.useragent) {
opts.userAgent = params.useragent opts.userAgent = params.useragent;
} }
this.loadURL(params.src, opts) this.loadURL(params.src, opts);
} }
embedder.emit('did-attach-webview', event, guest) embedder.emit('did-attach-webview', event, guest);
}) });
const sendToEmbedder = (channel, ...args) => { const sendToEmbedder = (channel, ...args) => {
if (!embedder.isDestroyed()) { if (!embedder.isDestroyed()) {
embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args) embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args);
}
} }
};
// Dispatch events to embedder. // Dispatch events to embedder.
const fn = function (event) { const fn = function (event) {
guest.on(event, function (_, ...args) { guest.on(event, function (_, ...args) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args) sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args);
}) });
} };
for (const event of supportedWebViewEvents) { for (const event of supportedWebViewEvents) {
fn(event) fn(event);
} }
guest.on('new-window', function (event, url, frameName, disposition, options, additionalFeatures, referrer) { guest.on('new-window', function (event, url, frameName, disposition, options, additionalFeatures, referrer) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', 'new-window', url, sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', 'new-window', url,
frameName, disposition, sanitizeOptionsForGuest(options), frameName, disposition, sanitizeOptionsForGuest(options),
additionalFeatures, referrer) additionalFeatures, referrer);
}) });
// Dispatch guest's IPC messages to embedder. // Dispatch guest's IPC messages to embedder.
guest.on('ipc-message-host', function (_, channel, args) { guest.on('ipc-message-host', function (_, channel, args) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args) sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args);
}) });
// Notify guest of embedder window visibility when it is ready // Notify guest of embedder window visibility when it is ready
// FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed // FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
guest.on('dom-ready', function () { guest.on('dom-ready', function () {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (guestInstance != null && guestInstance.visibilityState != null) { if (guestInstance != null && guestInstance.visibilityState != null) {
guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState) guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState);
} }
}) });
// Forward internal web contents event to embedder to handle // Forward internal web contents event to embedder to handle
// native window.open setup // native window.open setup
guest.on('-add-new-contents', (...args) => { guest.on('-add-new-contents', (...args) => {
if (guest.getLastWebPreferences().nativeWindowOpen === true) { if (guest.getLastWebPreferences().nativeWindowOpen === true) {
const embedder = getEmbedder(guestInstanceId) const embedder = getEmbedder(guestInstanceId);
if (embedder != null) { if (embedder != null) {
embedder.emit('-add-new-contents', ...args) embedder.emit('-add-new-contents', ...args);
} }
} }
}) });
return guestInstanceId return guestInstanceId;
} };
// Attach the guest to an element of embedder. // Attach the guest to an element of embedder.
const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
const embedder = event.sender const embedder = event.sender;
// Destroy the old guest when attaching. // Destroy the old guest when attaching.
const key = `${embedder.id}-${elementInstanceId}` const key = `${embedder.id}-${elementInstanceId}`;
const oldGuestInstanceId = embedderElementsMap[key] const oldGuestInstanceId = embedderElementsMap[key];
if (oldGuestInstanceId != null) { if (oldGuestInstanceId != null) {
// Reattachment to the same guest is just a no-op. // Reattachment to the same guest is just a no-op.
if (oldGuestInstanceId === guestInstanceId) { if (oldGuestInstanceId === guestInstanceId) {
return return;
} }
const oldGuestInstance = guestInstances[oldGuestInstanceId] const oldGuestInstance = guestInstances[oldGuestInstanceId];
if (oldGuestInstance) { if (oldGuestInstance) {
oldGuestInstance.guest.detachFromOuterFrame() oldGuestInstance.guest.detachFromOuterFrame();
} }
} }
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
// If this isn't a valid guest instance then do nothing. // If this isn't a valid guest instance then do nothing.
if (!guestInstance) { if (!guestInstance) {
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`) throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`);
} }
const { guest } = guestInstance const { guest } = guestInstance;
if (guest.hostWebContents !== event.sender) { if (guest.hostWebContents !== event.sender) {
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`) throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`);
} }
// If this guest is already attached to an element then remove it // If this guest is already attached to an element then remove it
if (guestInstance.elementInstanceId) { if (guestInstance.elementInstanceId) {
const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}` const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}`;
delete embedderElementsMap[oldKey] delete embedderElementsMap[oldKey];
// Remove guest from embedder if moving across web views // Remove guest from embedder if moving across web views
if (guest.viewInstanceId !== params.instanceId) { if (guest.viewInstanceId !== params.instanceId) {
webViewManager.removeGuest(guestInstance.embedder, guestInstanceId) webViewManager.removeGuest(guestInstance.embedder, guestInstanceId);
guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`) guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`);
} }
} }
@@ -206,7 +206,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
webSecurity: !params.disablewebsecurity, webSecurity: !params.disablewebsecurity,
enableBlinkFeatures: params.blinkfeatures, enableBlinkFeatures: params.blinkfeatures,
disableBlinkFeatures: params.disableblinkfeatures disableBlinkFeatures: params.disableblinkfeatures
} };
// parse the 'webpreferences' attribute string, if set // parse the 'webpreferences' attribute string, if set
// this uses the same parsing rules as window.open uses for its features // this uses the same parsing rules as window.open uses for its features
@@ -214,14 +214,14 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
parseFeaturesString(params.webpreferences, function (key, value) { parseFeaturesString(params.webpreferences, function (key, value) {
if (value === undefined) { if (value === undefined) {
// no value was specified, default it to true // no value was specified, default it to true
value = true value = true;
} }
webPreferences[key] = value webPreferences[key] = value;
}) });
} }
if (params.preload) { if (params.preload) {
webPreferences.preloadURL = params.preload webPreferences.preloadURL = params.preload;
} }
// Security options that guest will always inherit from embedder // Security options that guest will always inherit from embedder
@@ -233,185 +233,203 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
['enableRemoteModule', false], ['enableRemoteModule', false],
['sandbox', true], ['sandbox', true],
['nodeIntegrationInSubFrames', false] ['nodeIntegrationInSubFrames', false]
]) ]);
// Inherit certain option values from embedder // Inherit certain option values from embedder
const lastWebPreferences = embedder.getLastWebPreferences() const lastWebPreferences = embedder.getLastWebPreferences();
for (const [name, value] of inheritedWebPreferences) { for (const [name, value] of inheritedWebPreferences) {
if (lastWebPreferences[name] === value) { if (lastWebPreferences[name] === value) {
webPreferences[name] = value webPreferences[name] = value;
} }
} }
embedder.emit('will-attach-webview', event, webPreferences, params) embedder.emit('will-attach-webview', event, webPreferences, params);
if (event.defaultPrevented) { if (event.defaultPrevented) {
if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId;
guest.destroy() guest.destroy();
return return;
} }
guest.attachParams = params guest.attachParams = params;
embedderElementsMap[key] = guestInstanceId embedderElementsMap[key] = guestInstanceId;
guest.setEmbedder(embedder) guest.setEmbedder(embedder);
guestInstance.embedder = embedder guestInstance.embedder = embedder;
guestInstance.elementInstanceId = elementInstanceId guestInstance.elementInstanceId = elementInstanceId;
watchEmbedder(embedder) watchEmbedder(embedder);
webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences);
guest.attachToIframe(embedder, embedderFrameId) guest.attachToIframe(embedder, embedderFrameId);
} };
// Remove an guest-embedder relationship. // Remove an guest-embedder relationship.
const detachGuest = function (embedder, guestInstanceId) { const detachGuest = function (embedder, guestInstanceId) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (embedder !== guestInstance.embedder) { if (embedder !== guestInstance.embedder) {
return return;
} }
webViewManager.removeGuest(embedder, guestInstanceId) webViewManager.removeGuest(embedder, guestInstanceId);
delete guestInstances[guestInstanceId] delete guestInstances[guestInstanceId];
const key = `${embedder.id}-${guestInstance.elementInstanceId}` const key = `${embedder.id}-${guestInstance.elementInstanceId}`;
delete embedderElementsMap[key] delete embedderElementsMap[key];
} };
// Once an embedder has had a guest attached we watch it for destruction to // Once an embedder has had a guest attached we watch it for destruction to
// destroy any remaining guests. // destroy any remaining guests.
const watchedEmbedders = new Set() const watchedEmbedders = new Set();
const watchEmbedder = function (embedder) { const watchEmbedder = function (embedder) {
if (watchedEmbedders.has(embedder)) { if (watchedEmbedders.has(embedder)) {
return return;
} }
watchedEmbedders.add(embedder) watchedEmbedders.add(embedder);
// Forward embedder window visiblity change events to guest // Forward embedder window visiblity change events to guest
const onVisibilityChange = function (visibilityState) { const onVisibilityChange = function (visibilityState) {
for (const guestInstanceId in guestInstances) { for (const guestInstanceId of Object.keys(guestInstances)) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
guestInstance.visibilityState = visibilityState guestInstance.visibilityState = visibilityState;
if (guestInstance.embedder === embedder) { if (guestInstance.embedder === embedder) {
guestInstance.guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState) guestInstance.guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState);
} }
} }
} };
embedder.on('-window-visibility-change', onVisibilityChange) embedder.on('-window-visibility-change', onVisibilityChange);
embedder.once('will-destroy', () => { embedder.once('will-destroy', () => {
// Usually the guestInstances is cleared when guest is destroyed, but it // Usually the guestInstances is cleared when guest is destroyed, but it
// may happen that the embedder gets manually destroyed earlier than guest, // may happen that the embedder gets manually destroyed earlier than guest,
// and the embedder will be invalid in the usual code path. // and the embedder will be invalid in the usual code path.
for (const guestInstanceId in guestInstances) { for (const guestInstanceId of Object.keys(guestInstances)) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (guestInstance.embedder === embedder) { if (guestInstance.embedder === embedder) {
detachGuest(embedder, parseInt(guestInstanceId)) detachGuest(embedder, parseInt(guestInstanceId));
} }
} }
// Clear the listeners. // Clear the listeners.
embedder.removeListener('-window-visibility-change', onVisibilityChange) embedder.removeListener('-window-visibility-change', onVisibilityChange);
watchedEmbedders.delete(embedder) watchedEmbedders.delete(embedder);
}) });
} };
const isWebViewTagEnabledCache = new WeakMap() const isWebViewTagEnabledCache = new WeakMap();
const isWebViewTagEnabled = function (contents) { const isWebViewTagEnabled = function (contents) {
if (!isWebViewTagEnabledCache.has(contents)) { if (!isWebViewTagEnabledCache.has(contents)) {
const webPreferences = contents.getLastWebPreferences() || {} const webPreferences = contents.getLastWebPreferences() || {};
isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag) isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag);
} }
return isWebViewTagEnabledCache.get(contents) return isWebViewTagEnabledCache.get(contents);
} };
const makeSafeHandler = function (channel, handler) { const makeSafeHandler = function (channel, handler) {
return (event, ...args) => { return (event, ...args) => {
if (isWebViewTagEnabled(event.sender)) { if (isWebViewTagEnabled(event.sender)) {
return handler(event, ...args) return handler(event, ...args);
} else { } else {
console.error(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`) console.error(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`);
throw new Error('<webview> disabled') throw new Error('<webview> disabled');
}
}
} }
};
};
const handleMessage = function (channel, handler) { const handleMessage = function (channel, handler) {
ipcMainInternal.handle(channel, makeSafeHandler(channel, handler)) ipcMainInternal.handle(channel, makeSafeHandler(channel, handler));
} };
const handleMessageSync = function (channel, handler) { const handleMessageSync = function (channel, handler) {
ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler)) ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler));
} };
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params) { handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params) {
return createGuest(event.sender, params) return createGuest(event.sender, params);
}) });
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
try { try {
attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params) attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params);
} catch (error) { } catch (error) {
console.error(`Guest attach failed: ${error}`) console.error(`Guest attach failed: ${error}`);
} }
}) });
// this message is sent by the actual <webview> // this message is sent by the actual <webview>
ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) { ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) {
const guest = getGuest(guestInstanceId) const guest = getGuest(guestInstanceId);
if (guest === event.sender) { if (guest === event.sender) {
event.sender.emit('focus-change', {}, focus, guestInstanceId) event.sender.emit('focus-change', {}, focus, guestInstanceId);
} else { } else {
console.error(`focus-change for guestInstanceId: ${guestInstanceId}`) console.error(`focus-change for guestInstanceId: ${guestInstanceId}`);
} }
}) });
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) { handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) {
const guest = getGuestForWebContents(guestInstanceId, event.sender) const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!asyncMethods.has(method)) { if (!asyncMethods.has(method)) {
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return guest[method](...args) return guest[method](...args);
}) });
handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) { handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) {
const guest = getGuestForWebContents(guestInstanceId, event.sender) const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!syncMethods.has(method)) { if (!syncMethods.has(method)) {
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return guest[method](...args) return guest[method](...args);
}) });
handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_GET', function (event, guestInstanceId, property) {
const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!properties.has(property)) {
throw new Error(`Invalid property: ${property}`);
}
return guest[property];
});
handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_SET', function (event, guestInstanceId, property, val) {
const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!properties.has(property)) {
throw new Error(`Invalid property: ${property}`);
}
guest[property] = val;
});
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId, args) { handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId, args) {
const guest = getGuestForWebContents(guestInstanceId, event.sender) const guest = getGuestForWebContents(guestInstanceId, event.sender);
return serialize(await guest.capturePage(...args)) return serialize(await guest.capturePage(...args));
}) });
// Returns WebContents from its guest id hosted in given webContents. // Returns WebContents from its guest id hosted in given webContents.
const getGuestForWebContents = function (guestInstanceId, contents) { const getGuestForWebContents = function (guestInstanceId, contents) {
const guest = getGuest(guestInstanceId) const guest = getGuest(guestInstanceId);
if (!guest) { if (!guest) {
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`) throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`);
} }
if (guest.hostWebContents !== contents) { if (guest.hostWebContents !== contents) {
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`) throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`);
}
return guest
} }
return guest;
};
// Returns WebContents from its guest id. // Returns WebContents from its guest id.
const getGuest = function (guestInstanceId) { const getGuest = function (guestInstanceId) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (guestInstance != null) return guestInstance.guest if (guestInstance != null) return guestInstance.guest;
} };
// Returns the embedder of the guest. // Returns the embedder of the guest.
const getEmbedder = function (guestInstanceId) { const getEmbedder = function (guestInstanceId) {
const guestInstance = guestInstances[guestInstanceId] const guestInstance = guestInstances[guestInstanceId];
if (guestInstance != null) return guestInstance.embedder if (guestInstance != null) return guestInstance.embedder;
} };
exports.isWebViewTagEnabled = isWebViewTagEnabled exports.isWebViewTagEnabled = isWebViewTagEnabled;

View File

@@ -1,14 +1,14 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const { BrowserWindow } = electron const { BrowserWindow } = electron;
const { isSameOrigin } = process.electronBinding('v8_util') const { isSameOrigin } = process.electronBinding('v8_util');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const parseFeaturesString = require('@electron/internal/common/parse-features-string') const parseFeaturesString = require('@electron/internal/common/parse-features-string');
const hasProp = {}.hasOwnProperty const hasProp = {}.hasOwnProperty;
const frameToGuest = new Map() const frameToGuest = new Map();
// Security options that child windows will always inherit from parent windows // Security options that child windows will always inherit from parent windows
const inheritedWebPreferences = new Map([ const inheritedWebPreferences = new Map([
@@ -20,109 +20,109 @@ const inheritedWebPreferences = new Map([
['sandbox', true], ['sandbox', true],
['webviewTag', false], ['webviewTag', false],
['nodeIntegrationInSubFrames', false] ['nodeIntegrationInSubFrames', false]
]) ]);
// Copy attribute of |parent| to |child| if it is not defined in |child|. // Copy attribute of |parent| to |child| if it is not defined in |child|.
const mergeOptions = function (child, parent, visited) { const mergeOptions = function (child, parent, visited) {
// Check for circular reference. // Check for circular reference.
if (visited == null) visited = new Set() if (visited == null) visited = new Set();
if (visited.has(parent)) return if (visited.has(parent)) return;
visited.add(parent) visited.add(parent);
for (const key in parent) { for (const key in parent) {
if (key === 'type') continue if (key === 'type') continue;
if (!hasProp.call(parent, key)) continue if (!hasProp.call(parent, key)) continue;
if (key in child && key !== 'webPreferences') continue if (key in child && key !== 'webPreferences') continue;
const value = parent[key] const value = parent[key];
if (typeof value === 'object' && !Array.isArray(value)) { if (typeof value === 'object' && !Array.isArray(value)) {
child[key] = mergeOptions(child[key] || {}, value, visited) child[key] = mergeOptions(child[key] || {}, value, visited);
} else { } else {
child[key] = value child[key] = value;
} }
} }
visited.delete(parent) visited.delete(parent);
return child return child;
} };
// Merge |options| with the |embedder|'s window's options. // Merge |options| with the |embedder|'s window's options.
const mergeBrowserWindowOptions = function (embedder, options) { const mergeBrowserWindowOptions = function (embedder, options) {
if (options.webPreferences == null) { if (options.webPreferences == null) {
options.webPreferences = {} options.webPreferences = {};
} }
if (embedder.browserWindowOptions != null) { if (embedder.browserWindowOptions != null) {
let parentOptions = embedder.browserWindowOptions let parentOptions = embedder.browserWindowOptions;
// if parent's visibility is available, that overrides 'show' flag (#12125) // if parent's visibility is available, that overrides 'show' flag (#12125)
const win = BrowserWindow.fromWebContents(embedder.webContents) const win = BrowserWindow.fromWebContents(embedder.webContents);
if (win != null) { if (win != null) {
parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() } parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() };
} }
// Inherit the original options if it is a BrowserWindow. // Inherit the original options if it is a BrowserWindow.
mergeOptions(options, parentOptions) mergeOptions(options, parentOptions);
} else { } else {
// Or only inherit webPreferences if it is a webview. // Or only inherit webPreferences if it is a webview.
mergeOptions(options.webPreferences, embedder.getLastWebPreferences()) mergeOptions(options.webPreferences, embedder.getLastWebPreferences());
} }
// Inherit certain option values from parent window // Inherit certain option values from parent window
const webPreferences = embedder.getLastWebPreferences() const webPreferences = embedder.getLastWebPreferences();
for (const [name, value] of inheritedWebPreferences) { for (const [name, value] of inheritedWebPreferences) {
if (webPreferences[name] === value) { if (webPreferences[name] === value) {
options.webPreferences[name] = value options.webPreferences[name] = value;
} }
} }
if (!webPreferences.nativeWindowOpen) { if (!webPreferences.nativeWindowOpen) {
// Sets correct openerId here to give correct options to 'new-window' event handler // Sets correct openerId here to give correct options to 'new-window' event handler
options.webPreferences.openerId = embedder.id options.webPreferences.openerId = embedder.id;
} }
return options return options;
} };
// Setup a new guest with |embedder| // Setup a new guest with |embedder|
const setupGuest = function (embedder, frameName, guest, options) { const setupGuest = function (embedder, frameName, guest, options) {
// When |embedder| is destroyed we should also destroy attached guest, and if // When |embedder| is destroyed we should also destroy attached guest, and if
// guest is closed by user then we should prevent |embedder| from double // guest is closed by user then we should prevent |embedder| from double
// closing guest. // closing guest.
const guestId = guest.webContents.id const guestId = guest.webContents.id;
const closedByEmbedder = function () { const closedByEmbedder = function () {
guest.removeListener('closed', closedByUser) guest.removeListener('closed', closedByUser);
guest.destroy() guest.destroy();
} };
const closedByUser = function () { const closedByUser = function () {
embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId) embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId);
embedder.removeListener('current-render-view-deleted', closedByEmbedder) embedder.removeListener('current-render-view-deleted', closedByEmbedder);
} };
embedder.once('current-render-view-deleted', closedByEmbedder) embedder.once('current-render-view-deleted', closedByEmbedder);
guest.once('closed', closedByUser) guest.once('closed', closedByUser);
if (frameName) { if (frameName) {
frameToGuest.set(frameName, guest) frameToGuest.set(frameName, guest);
guest.frameName = frameName guest.frameName = frameName;
guest.once('closed', function () { guest.once('closed', function () {
frameToGuest.delete(frameName) frameToGuest.delete(frameName);
}) });
}
return guestId
} }
return guestId;
};
// Create a new guest created by |embedder| with |options|. // Create a new guest created by |embedder| with |options|.
const createGuest = function (embedder, url, referrer, frameName, options, postData) { const createGuest = function (embedder, url, referrer, frameName, options, postData) {
let guest = frameToGuest.get(frameName) let guest = frameToGuest.get(frameName);
if (frameName && (guest != null)) { if (frameName && (guest != null)) {
guest.loadURL(url) guest.loadURL(url);
return guest.webContents.id return guest.webContents.id;
} }
// Remember the embedder window's id. // Remember the embedder window's id.
if (options.webPreferences == null) { if (options.webPreferences == null) {
options.webPreferences = {} options.webPreferences = {};
} }
guest = new BrowserWindow(options) guest = new BrowserWindow(options);
if (!options.webContents) { if (!options.webContents) {
// We should not call `loadURL` if the window was constructed from an // We should not call `loadURL` if the window was constructed from an
// existing webContents (window.open in a sandboxed renderer). // existing webContents (window.open in a sandboxed renderer).
@@ -131,236 +131,244 @@ const createGuest = function (embedder, url, referrer, frameName, options, postD
// webContents is not necessary (it will navigate there anyway). // webContents is not necessary (it will navigate there anyway).
const loadOptions = { const loadOptions = {
httpReferrer: referrer httpReferrer: referrer
} };
if (postData != null) { if (postData != null) {
loadOptions.postData = postData loadOptions.postData = postData;
loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded' loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded';
if (postData.length > 0) { if (postData.length > 0) {
const postDataFront = postData[0].bytes.toString() const postDataFront = postData[0].bytes.toString();
const boundary = /^--.*[^-\r\n]/.exec(postDataFront) const boundary = /^--.*[^-\r\n]/.exec(postDataFront);
if (boundary != null) { if (boundary != null) {
loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}` loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`;
} }
} }
} }
guest.loadURL(url, loadOptions) guest.loadURL(url, loadOptions);
} }
return setupGuest(embedder, frameName, guest, options) return setupGuest(embedder, frameName, guest, options);
} };
const getGuestWindow = function (guestContents) { const getGuestWindow = function (guestContents) {
let guestWindow = BrowserWindow.fromWebContents(guestContents) let guestWindow = BrowserWindow.fromWebContents(guestContents);
if (guestWindow == null) { if (guestWindow == null) {
const hostContents = guestContents.hostWebContents const hostContents = guestContents.hostWebContents;
if (hostContents != null) { if (hostContents != null) {
guestWindow = BrowserWindow.fromWebContents(hostContents) guestWindow = BrowserWindow.fromWebContents(hostContents);
} }
} }
if (!guestWindow) { if (!guestWindow) {
throw new Error('getGuestWindow failed') throw new Error('getGuestWindow failed');
}
return guestWindow
} }
return guestWindow;
};
const isChildWindow = function (sender, target) { const isChildWindow = function (sender, target) {
return target.getLastWebPreferences().openerId === sender.id return target.getLastWebPreferences().openerId === sender.id;
} };
const isRelatedWindow = function (sender, target) { const isRelatedWindow = function (sender, target) {
return isChildWindow(sender, target) || isChildWindow(target, sender) return isChildWindow(sender, target) || isChildWindow(target, sender);
} };
const isScriptableWindow = function (sender, target) { const isScriptableWindow = function (sender, target) {
return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL()) return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL());
} };
const isNodeIntegrationEnabled = function (sender) { const isNodeIntegrationEnabled = function (sender) {
return sender.getLastWebPreferences().nodeIntegration === true return sender.getLastWebPreferences().nodeIntegration === true;
} };
// Checks whether |sender| can access the |target|: // Checks whether |sender| can access the |target|:
const canAccessWindow = function (sender, target) { const canAccessWindow = function (sender, target) {
return isChildWindow(sender, target) || return isChildWindow(sender, target) ||
isScriptableWindow(sender, target) || isScriptableWindow(sender, target) ||
isNodeIntegrationEnabled(sender) isNodeIntegrationEnabled(sender);
} };
// Routed window.open messages with raw options // Routed window.open messages with raw options
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => { ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
if (url == null || url === '') url = 'about:blank' // This should only be allowed for senders that have nativeWindowOpen: false
if (frameName == null) frameName = '' {
if (features == null) features = '' const webPreferences = event.sender.getLastWebPreferences();
if (webPreferences.nativeWindowOpen || webPreferences.sandbox) {
event.returnValue = null;
throw new Error('GUEST_WINDOW_MANAGER_WINDOW_OPEN denied: expected native window.open');
}
}
if (url == null || url === '') url = 'about:blank';
if (frameName == null) frameName = '';
if (features == null) features = '';
const options = {} const options = {};
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'] const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'];
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag'] const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'javascript', 'contextIsolation', 'webviewTag'];
const disposition = 'new-window' const disposition = 'new-window';
// Used to store additional features // Used to store additional features
const additionalFeatures = [] const additionalFeatures = [];
// Parse the features // Parse the features
parseFeaturesString(features, function (key, value) { parseFeaturesString(features, function (key, value) {
if (value === undefined) { if (value === undefined) {
additionalFeatures.push(key) additionalFeatures.push(key);
} else { } else {
// Don't allow webPreferences to be set since it must be an object // Don't allow webPreferences to be set since it must be an object
// that cannot be directly overridden // that cannot be directly overridden
if (key === 'webPreferences') return if (key === 'webPreferences') return;
if (webPreferences.includes(key)) { if (webPreferences.includes(key)) {
if (options.webPreferences == null) { if (options.webPreferences == null) {
options.webPreferences = {} options.webPreferences = {};
} }
options.webPreferences[key] = value options.webPreferences[key] = value;
} else { } else {
options[key] = value options[key] = value;
} }
} }
}) });
if (options.left) { if (options.left) {
if (options.x == null) { if (options.x == null) {
options.x = options.left options.x = options.left;
} }
} }
if (options.top) { if (options.top) {
if (options.y == null) { if (options.y == null) {
options.y = options.top options.y = options.top;
} }
} }
if (options.title == null) { if (options.title == null) {
options.title = frameName options.title = frameName;
} }
if (options.width == null) { if (options.width == null) {
options.width = 800 options.width = 800;
} }
if (options.height == null) { if (options.height == null) {
options.height = 600 options.height = 600;
} }
for (const name of ints) { for (const name of ints) {
if (options[name] != null) { if (options[name] != null) {
options[name] = parseInt(options[name], 10) options[name] = parseInt(options[name], 10);
} }
} }
const referrer = { url: '', policy: 'default' } const referrer = { url: '', policy: 'default' };
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures) internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures);
}) });
// Routed window.open messages with fully parsed options // Routed window.open messages with fully parsed options
function internalWindowOpen (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) { function internalWindowOpen (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) {
options = mergeBrowserWindowOptions(event.sender, options) options = mergeBrowserWindowOptions(event.sender, options);
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer) event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer);
const { newGuest } = event const { newGuest } = event;
if ((event.sender.getType() === 'webview' && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) { if ((event.sender.getType() === 'webview' && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) {
if (newGuest != null) { if (newGuest != null) {
if (options.webContents === newGuest.webContents) { if (options.webContents === newGuest.webContents) {
// the webContents is not changed, so set defaultPrevented to false to // the webContents is not changed, so set defaultPrevented to false to
// stop the callers of this event from destroying the webContents. // stop the callers of this event from destroying the webContents.
event.defaultPrevented = false event.defaultPrevented = false;
} }
event.returnValue = setupGuest(event.sender, frameName, newGuest, options) event.returnValue = setupGuest(event.sender, frameName, newGuest, options);
} else { } else {
event.returnValue = null event.returnValue = null;
} }
} else { } else {
event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData) event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData);
} }
} }
const makeSafeHandler = function (handler) { const makeSafeHandler = function (handler) {
return (event, guestId, ...args) => { return (event, guestId, ...args) => {
// Access webContents via electron to prevent circular require. // Access webContents via electron to prevent circular require.
const guestContents = electron.webContents.fromId(guestId) const guestContents = electron.webContents.fromId(guestId);
if (!guestContents) { if (!guestContents) {
throw new Error(`Invalid guestId: ${guestId}`) throw new Error(`Invalid guestId: ${guestId}`);
} }
return handler(event, guestContents, ...args) return handler(event, guestContents, ...args);
} };
} };
const handleMessage = function (channel, handler) { const handleMessage = function (channel, handler) {
ipcMainInternal.handle(channel, makeSafeHandler(handler)) ipcMainInternal.handle(channel, makeSafeHandler(handler));
} };
const handleMessageSync = function (channel, handler) { const handleMessageSync = function (channel, handler) {
ipcMainUtils.handleSync(channel, makeSafeHandler(handler)) ipcMainUtils.handleSync(channel, makeSafeHandler(handler));
} };
const securityCheck = function (contents, guestContents, check) { const securityCheck = function (contents, guestContents, check) {
if (!check(contents, guestContents)) { if (!check(contents, guestContents)) {
console.error(`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`) console.error(`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`);
throw new Error(`Access denied to guestId: ${guestContents.id}`) throw new Error(`Access denied to guestId: ${guestContents.id}`);
}
} }
};
const windowMethods = new Set([ const windowMethods = new Set([
'destroy', 'destroy',
'focus', 'focus',
'blur' 'blur'
]) ]);
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestContents, method, ...args) => { handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestContents, method, ...args) => {
securityCheck(event.sender, guestContents, canAccessWindow) securityCheck(event.sender, guestContents, canAccessWindow);
if (!windowMethods.has(method)) { if (!windowMethods.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`) console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`);
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return getGuestWindow(guestContents)[method](...args) return getGuestWindow(guestContents)[method](...args);
}) });
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestContents, message, targetOrigin, sourceOrigin) => { handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestContents, message, targetOrigin, sourceOrigin) => {
if (targetOrigin == null) { if (targetOrigin == null) {
targetOrigin = '*' targetOrigin = '*';
} }
// The W3C does not seem to have word on how postMessage should work when the // The W3C does not seem to have word on how postMessage should work when the
// origins do not match, so we do not do |canAccessWindow| check here since // origins do not match, so we do not do |canAccessWindow| check here since
// postMessage across origins is useful and not harmful. // postMessage across origins is useful and not harmful.
securityCheck(event.sender, guestContents, isRelatedWindow) securityCheck(event.sender, guestContents, isRelatedWindow);
if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) { if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
const sourceId = event.sender.id const sourceId = event.sender.id;
guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin);
} }
}) });
const webContentsMethodsAsync = new Set([ const webContentsMethodsAsync = new Set([
'loadURL', 'loadURL',
'executeJavaScript', 'executeJavaScript',
'print' 'print'
]) ]);
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => { handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => {
securityCheck(event.sender, guestContents, canAccessWindow) securityCheck(event.sender, guestContents, canAccessWindow);
if (!webContentsMethodsAsync.has(method)) { if (!webContentsMethodsAsync.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`) console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`);
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return guestContents[method](...args) return guestContents[method](...args);
}) });
const webContentsMethodsSync = new Set([ const webContentsMethodsSync = new Set([
'getURL' 'getURL'
]) ]);
handleMessageSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => { handleMessageSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => {
securityCheck(event.sender, guestContents, canAccessWindow) securityCheck(event.sender, guestContents, canAccessWindow);
if (!webContentsMethodsSync.has(method)) { if (!webContentsMethodsSync.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`) console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`);
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return guestContents[method](...args) return guestContents[method](...args);
}) });
exports.internalWindowOpen = internalWindowOpen exports.internalWindowOpen = internalWindowOpen;

View File

@@ -1,45 +1,47 @@
import { Buffer } from 'buffer' import { Buffer } from 'buffer';
import * as fs from 'fs' import * as fs from 'fs';
import * as path from 'path' import * as path from 'path';
import * as util from 'util' import * as util from 'util';
const Module = require('module') const Module = require('module');
// We modified the original process.argv to let node.js load the init.js, // We modified the original process.argv to let node.js load the init.js,
// we need to restore it here. // we need to restore it here.
process.argv.splice(1, 1) process.argv.splice(1, 1);
// Clear search paths. // Clear search paths.
require('../common/reset-search-paths') require('../common/reset-search-paths');
// Import common settings. // Import common settings.
require('@electron/internal/common/init') require('@electron/internal/common/init');
if (process.platform === 'win32') { if (process.platform === 'win32') {
// Redirect node's console to use our own implementations, since node can not // Redirect node's console to use our own implementations, since node can not
// handle console output when running as GUI program. // handle console output when running as GUI program.
const consoleLog = (format: any, ...args: any[]) => { const consoleLog = (...args: any[]) => {
return process.log(util.format(format, ...args) + '\n') // @ts-ignore this typing is incorrect; 'format' is an optional parameter
} // See https://nodejs.org/api/util.html#util_util_format_format_args
return process.log(util.format(...args) + '\n');
};
const streamWrite: NodeJS.WritableStream['write'] = function (chunk: Buffer | string, encoding?: any, callback?: Function) { const streamWrite: NodeJS.WritableStream['write'] = function (chunk: Buffer | string, encoding?: any, callback?: Function) {
if (Buffer.isBuffer(chunk)) { if (Buffer.isBuffer(chunk)) {
chunk = chunk.toString(encoding) chunk = chunk.toString(encoding);
} }
process.log(chunk) process.log(chunk);
if (callback) { if (callback) {
callback() callback();
} }
return true return true;
} };
console.log = console.error = console.warn = consoleLog console.log = console.error = console.warn = consoleLog;
process.stdout.write = process.stderr.write = streamWrite process.stdout.write = process.stderr.write = streamWrite;
} }
// Don't quit on fatal error. // Don't quit on fatal error.
process.on('uncaughtException', function (error) { process.on('uncaughtException', function (error) {
// Do nothing if the user has a custom uncaught exception handler. // Do nothing if the user has a custom uncaught exception handler.
if (process.listenerCount('uncaughtException') > 1) { if (process.listenerCount('uncaughtException') > 1) {
return return;
} }
// Show error in GUI. // Show error in GUI.
@@ -48,18 +50,18 @@ process.on('uncaughtException', function (error) {
// so we import it inside the handler down here // so we import it inside the handler down here
import('electron') import('electron')
.then(({ dialog }) => { .then(({ dialog }) => {
const stack = error.stack ? error.stack : `${error.name}: ${error.message}` const stack = error.stack ? error.stack : `${error.name}: ${error.message}`;
const message = 'Uncaught Exception:\n' + stack const message = 'Uncaught Exception:\n' + stack;
dialog.showErrorBox('A JavaScript error occurred in the main process', message) dialog.showErrorBox('A JavaScript error occurred in the main process', message);
}) });
}) });
// Emit 'exit' event on quit. // Emit 'exit' event on quit.
const { app } = require('electron') const { app } = require('electron');
app.on('quit', function (event, exitCode) { app.on('quit', function (event, exitCode) {
process.emit('exit', exitCode) process.emit('exit', exitCode);
}) });
if (process.platform === 'win32') { if (process.platform === 'win32') {
// If we are a Squirrel.Windows-installed app, set app user model ID // If we are a Squirrel.Windows-installed app, set app user model ID
@@ -76,141 +78,141 @@ if (process.platform === 'win32') {
// form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call // form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call
// app.setAppUserModelId with a matching identifier so that renderer processes // app.setAppUserModelId with a matching identifier so that renderer processes
// will inherit this value. // will inherit this value.
const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe') const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe');
if (fs.existsSync(updateDotExe)) { if (fs.existsSync(updateDotExe)) {
const packageDir = path.dirname(path.resolve(updateDotExe)) const packageDir = path.dirname(path.resolve(updateDotExe));
const packageName = path.basename(packageDir).replace(/\s/g, '') const packageName = path.basename(packageDir).replace(/\s/g, '');
const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '') const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '');
app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`) app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`);
} }
} }
// Map process.exit to app.exit, which quits gracefully. // Map process.exit to app.exit, which quits gracefully.
process.exit = app.exit as () => never process.exit = app.exit as () => never;
// Load the RPC server. // Load the RPC server.
require('@electron/internal/browser/rpc-server') require('@electron/internal/browser/rpc-server');
// Load the guest view manager. // Load the guest view manager.
require('@electron/internal/browser/guest-view-manager') require('@electron/internal/browser/guest-view-manager');
require('@electron/internal/browser/guest-window-manager') require('@electron/internal/browser/guest-window-manager');
// Now we try to load app's package.json. // Now we try to load app's package.json.
let packagePath = null let packagePath = null;
let packageJson = null let packageJson = null;
const searchPaths = ['app', 'app.asar', 'default_app.asar'] const searchPaths = ['app', 'app.asar', 'default_app.asar'];
if (process.resourcesPath) { if (process.resourcesPath) {
for (packagePath of searchPaths) { for (packagePath of searchPaths) {
try { try {
packagePath = path.join(process.resourcesPath, packagePath) packagePath = path.join(process.resourcesPath, packagePath);
packageJson = Module._load(path.join(packagePath, 'package.json')) packageJson = Module._load(path.join(packagePath, 'package.json'));
break break;
} catch { } catch {
continue continue;
} }
} }
} }
if (packageJson == null) { if (packageJson == null) {
process.nextTick(function () { process.nextTick(function () {
return process.exit(1) return process.exit(1);
}) });
throw new Error('Unable to find a valid app') throw new Error('Unable to find a valid app');
} }
// Set application's version. // Set application's version.
if (packageJson.version != null) { if (packageJson.version != null) {
app.setVersion(packageJson.version) app.setVersion(packageJson.version);
} }
// Set application's name. // Set application's name.
if (packageJson.productName != null) { if (packageJson.productName != null) {
app.name = `${packageJson.productName}`.trim() app.name = `${packageJson.productName}`.trim();
} else if (packageJson.name != null) { } else if (packageJson.name != null) {
app.name = `${packageJson.name}`.trim() app.name = `${packageJson.name}`.trim();
} }
// Set application's desktop name. // Set application's desktop name.
if (packageJson.desktopName != null) { if (packageJson.desktopName != null) {
app.setDesktopName(packageJson.desktopName) app.setDesktopName(packageJson.desktopName);
} else { } else {
app.setDesktopName(`${app.name}.desktop`) app.setDesktopName(`${app.name}.desktop`);
} }
// Set v8 flags, delibrately lazy load so that apps that do not use this // Set v8 flags, delibrately lazy load so that apps that do not use this
// feature do not pay the price // feature do not pay the price
if (packageJson.v8Flags != null) { if (packageJson.v8Flags != null) {
require('v8').setFlagsFromString(packageJson.v8Flags) require('v8').setFlagsFromString(packageJson.v8Flags);
} }
app._setDefaultAppPaths(packagePath) app._setDefaultAppPaths(packagePath);
// Load the chrome devtools support. // Load the chrome devtools support.
require('@electron/internal/browser/devtools') require('@electron/internal/browser/devtools');
const features = process.electronBinding('features') const features = process.electronBinding('features');
// Load the chrome extension support. // Load the chrome extension support.
if (features.isExtensionsEnabled()) { if (features.isExtensionsEnabled()) {
require('@electron/internal/browser/chrome-extension-shim') require('@electron/internal/browser/chrome-extension-shim');
} else { } else {
require('@electron/internal/browser/chrome-extension') require('@electron/internal/browser/chrome-extension');
} }
if (features.isRemoteModuleEnabled()) { if (features.isRemoteModuleEnabled()) {
require('@electron/internal/browser/remote/server') require('@electron/internal/browser/remote/server');
} }
// Load protocol module to ensure it is populated on app ready // Load protocol module to ensure it is populated on app ready
require('@electron/internal/browser/api/protocol') require('@electron/internal/browser/api/protocol');
// Set main startup script of the app. // Set main startup script of the app.
const mainStartupScript = packageJson.main || 'index.js' const mainStartupScript = packageJson.main || 'index.js';
const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME'] const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME'];
function currentPlatformSupportsAppIndicator () { function currentPlatformSupportsAppIndicator () {
if (process.platform !== 'linux') return false if (process.platform !== 'linux') return false;
const currentDesktop = process.env.XDG_CURRENT_DESKTOP const currentDesktop = process.env.XDG_CURRENT_DESKTOP;
if (!currentDesktop) return false if (!currentDesktop) return false;
if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) return true if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) return true;
// ubuntu based or derived session (default ubuntu one, communitheme…) supports // ubuntu based or derived session (default ubuntu one, communitheme…) supports
// indicator too. // indicator too.
if (/ubuntu/ig.test(currentDesktop)) return true if (/ubuntu/ig.test(currentDesktop)) return true;
return false return false;
} }
// Workaround for electron/electron#5050 and electron/electron#9046 // Workaround for electron/electron#5050 and electron/electron#9046
if (currentPlatformSupportsAppIndicator()) { if (currentPlatformSupportsAppIndicator()) {
process.env.XDG_CURRENT_DESKTOP = 'Unity' process.env.XDG_CURRENT_DESKTOP = 'Unity';
} }
// Quit when all windows are closed and no other one is listening to this. // Quit when all windows are closed and no other one is listening to this.
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
if (app.listenerCount('window-all-closed') === 1) { if (app.listenerCount('window-all-closed') === 1) {
app.quit() app.quit();
} }
}) });
const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu') const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu');
// Create default menu. // Create default menu.
// //
// Note that the task must be added before loading any app, so we can make sure // Note that the task must be added before loading any app, so we can make sure
// the call is maded before any user window is created, otherwise the default // the call is maded before any user window is created, otherwise the default
// menu may show even when user explicitly hides the menu. // menu may show even when user explicitly hides the menu.
app.whenReady().then(setDefaultApplicationMenu) app.whenReady().then(setDefaultApplicationMenu);
if (packagePath) { if (packagePath) {
// Finally load app's main.js and transfer control to C++. // Finally load app's main.js and transfer control to C++.
process._firstFileName = Module._resolveFilename(path.join(packagePath, mainStartupScript), null, false) process._firstFileName = Module._resolveFilename(path.join(packagePath, mainStartupScript), null, false);
Module._load(path.join(packagePath, mainStartupScript), Module, true) Module._load(path.join(packagePath, mainStartupScript), Module, true);
} else { } else {
console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)') console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)');
console.error('This normally means you\'ve damaged the Electron package somehow') console.error('This normally means you\'ve damaged the Electron package somehow');
} }

View File

@@ -1,33 +1,33 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
import { IpcMainInvokeEvent } from 'electron' import { IpcMainInvokeEvent } from 'electron';
export class IpcMainImpl extends EventEmitter { export class IpcMainImpl extends EventEmitter {
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map(); private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();
handle: Electron.IpcMain['handle'] = (method, fn) => { handle: Electron.IpcMain['handle'] = (method, fn) => {
if (this._invokeHandlers.has(method)) { if (this._invokeHandlers.has(method)) {
throw new Error(`Attempted to register a second handler for '${method}'`) throw new Error(`Attempted to register a second handler for '${method}'`);
} }
if (typeof fn !== 'function') { if (typeof fn !== 'function') {
throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`) throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`);
} }
this._invokeHandlers.set(method, async (e, ...args) => { this._invokeHandlers.set(method, async (e, ...args) => {
try { try {
(e as any)._reply(await Promise.resolve(fn(e, ...args))) (e as any)._reply(await Promise.resolve(fn(e, ...args)));
} catch (err) { } catch (err) {
(e as any)._throw(err) (e as any)._throw(err);
} }
}) });
} }
handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => { handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => {
this.handle(method, (e, ...args) => { this.handle(method, (e, ...args) => {
this.removeHandler(method) this.removeHandler(method);
return fn(e, ...args) return fn(e, ...args);
}) });
} }
removeHandler (method: string) { removeHandler (method: string) {
this._invokeHandlers.delete(method) this._invokeHandlers.delete(method);
} }
} }

View File

@@ -1,44 +1,44 @@
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal' import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
type IPCHandler = (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any type IPCHandler = (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any
export const handleSync = function <T extends IPCHandler> (channel: string, handler: T) { export const handleSync = function <T extends IPCHandler> (channel: string, handler: T) {
ipcMainInternal.on(channel, async (event, ...args) => { ipcMainInternal.on(channel, async (event, ...args) => {
try { try {
event.returnValue = [null, await handler(event, ...args)] event.returnValue = [null, await handler(event, ...args)];
} catch (error) { } catch (error) {
event.returnValue = [error] event.returnValue = [error];
}
})
} }
});
};
let nextId = 0 let nextId = 0;
export function invokeInWebContents<T> (sender: Electron.WebContentsInternal, sendToAll: boolean, command: string, ...args: any[]) { export function invokeInWebContents<T> (sender: Electron.WebContentsInternal, sendToAll: boolean, command: string, ...args: any[]) {
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
const requestId = ++nextId const requestId = ++nextId;
const channel = `${command}_RESPONSE_${requestId}` const channel = `${command}_RESPONSE_${requestId}`;
ipcMainInternal.on(channel, function handler ( ipcMainInternal.on(channel, function handler (
event, error: Electron.SerializedError, result: any event, error: Electron.SerializedError, result: any
) { ) {
if (event.sender !== sender) { if (event.sender !== sender) {
console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`) console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`);
return return;
} }
ipcMainInternal.removeListener(channel, handler) ipcMainInternal.removeListener(channel, handler);
if (error) { if (error) {
reject(error) reject(error);
} else { } else {
resolve(result) resolve(result);
} }
}) });
if (sendToAll) { if (sendToAll) {
sender._sendInternalToAll(command, requestId, ...args) sender._sendInternalToAll(command, requestId, ...args);
} else { } else {
sender._sendInternal(command, requestId, ...args) sender._sendInternal(command, requestId, ...args);
} }
}) });
} }

View File

@@ -1,6 +1,6 @@
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl' import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
export const ipcMainInternal = new IpcMainImpl() as ElectronInternal.IpcMainInternal export const ipcMainInternal = new IpcMainImpl() as ElectronInternal.IpcMainInternal;
// Do not throw exception when channel name is "error". // Do not throw exception when channel name is "error".
ipcMainInternal.on('error', () => {}) ipcMainInternal.on('error', () => {});

View File

@@ -1,23 +1,23 @@
'use strict' 'use strict';
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
// The history operation in renderer is redirected to browser. // The history operation in renderer is redirected to browser.
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) { ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) {
event.sender.goBack() event.sender.goBack();
}) });
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD', function (event) { ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD', function (event) {
event.sender.goForward() event.sender.goForward();
}) });
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', function (event, offset) { ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', function (event, offset) {
event.sender.goToOffset(offset) event.sender.goToOffset(offset);
}) });
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) { ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) {
event.returnValue = event.sender.length() event.returnValue = event.sender.length();
}) });
// JavaScript implementation of Chromium's NavigationController. // JavaScript implementation of Chromium's NavigationController.
// Instead of relying on Chromium for history control, we compeletely do history // Instead of relying on Chromium for history control, we compeletely do history
@@ -26,64 +26,64 @@ ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) {
// process is restarted everytime. // process is restarted everytime.
const NavigationController = (function () { const NavigationController = (function () {
function NavigationController (webContents) { function NavigationController (webContents) {
this.webContents = webContents this.webContents = webContents;
this.clearHistory() this.clearHistory();
// webContents may have already navigated to a page. // webContents may have already navigated to a page.
if (this.webContents._getURL()) { if (this.webContents._getURL()) {
this.currentIndex++ this.currentIndex++;
this.history.push(this.webContents._getURL()) this.history.push(this.webContents._getURL());
} }
this.webContents.on('navigation-entry-committed', (event, url, inPage, replaceEntry) => { this.webContents.on('navigation-entry-committed', (event, url, inPage, replaceEntry) => {
if (this.inPageIndex > -1 && !inPage) { if (this.inPageIndex > -1 && !inPage) {
// Navigated to a new page, clear in-page mark. // Navigated to a new page, clear in-page mark.
this.inPageIndex = -1 this.inPageIndex = -1;
} else if (this.inPageIndex === -1 && inPage && !replaceEntry) { } else if (this.inPageIndex === -1 && inPage && !replaceEntry) {
// Started in-page navigations. // Started in-page navigations.
this.inPageIndex = this.currentIndex this.inPageIndex = this.currentIndex;
} }
if (this.pendingIndex >= 0) { if (this.pendingIndex >= 0) {
// Go to index. // Go to index.
this.currentIndex = this.pendingIndex this.currentIndex = this.pendingIndex;
this.pendingIndex = -1 this.pendingIndex = -1;
this.history[this.currentIndex] = url this.history[this.currentIndex] = url;
} else if (replaceEntry) { } else if (replaceEntry) {
// Non-user initialized navigation. // Non-user initialized navigation.
this.history[this.currentIndex] = url this.history[this.currentIndex] = url;
} else { } else {
// Normal navigation. Clear history. // Normal navigation. Clear history.
this.history = this.history.slice(0, this.currentIndex + 1) this.history = this.history.slice(0, this.currentIndex + 1);
this.currentIndex++ this.currentIndex++;
this.history.push(url) this.history.push(url);
} }
}) });
} }
NavigationController.prototype.loadURL = function (url, options) { NavigationController.prototype.loadURL = function (url, options) {
if (options == null) { if (options == null) {
options = {} options = {};
} }
const p = new Promise((resolve, reject) => { const p = new Promise((resolve, reject) => {
const resolveAndCleanup = () => { const resolveAndCleanup = () => {
removeListeners() removeListeners();
resolve() resolve();
} };
const rejectAndCleanup = (errorCode, errorDescription, url) => { const rejectAndCleanup = (errorCode, errorDescription, url) => {
const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`) const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`);
Object.assign(err, { errno: errorCode, code: errorDescription, url }) Object.assign(err, { errno: errorCode, code: errorDescription, url });
removeListeners() removeListeners();
reject(err) reject(err);
} };
const finishListener = () => { const finishListener = () => {
resolveAndCleanup() resolveAndCleanup();
} };
const failListener = (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => { const failListener = (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
if (isMainFrame) { if (isMainFrame) {
rejectAndCleanup(errorCode, errorDescription, validatedURL) rejectAndCleanup(errorCode, errorDescription, validatedURL);
}
} }
};
let navigationStarted = false let navigationStarted = false;
const navigationListener = (event, url, isSameDocument, isMainFrame, frameProcessId, frameRoutingId, navigationId) => { const navigationListener = (event, url, isSameDocument, isMainFrame, frameProcessId, frameRoutingId, navigationId) => {
if (isMainFrame) { if (isMainFrame) {
if (navigationStarted && !isSameDocument) { if (navigationStarted && !isSameDocument) {
@@ -96,11 +96,11 @@ const NavigationController = (function () {
// considered navigation events but are triggered with isSameDocument. // considered navigation events but are triggered with isSameDocument.
// We can ignore these to allow virtual routing on page load as long // We can ignore these to allow virtual routing on page load as long
// as the routing does not leave the document // as the routing does not leave the document
return rejectAndCleanup(-3, 'ERR_ABORTED', url) return rejectAndCleanup(-3, 'ERR_ABORTED', url);
}
navigationStarted = true
} }
navigationStarted = true;
} }
};
const stopLoadingListener = () => { const stopLoadingListener = () => {
// By the time we get here, either 'finish' or 'fail' should have fired // By the time we get here, either 'finish' or 'fail' should have fired
// if the navigation occurred. However, in some situations (e.g. when // if the navigation occurred. However, in some situations (e.g. when
@@ -110,134 +110,134 @@ const NavigationController = (function () {
// TODO(jeremy): enumerate all the cases in which this can happen. If // TODO(jeremy): enumerate all the cases in which this can happen. If
// the only one is with a bad scheme, perhaps ERR_INVALID_ARGUMENT // the only one is with a bad scheme, perhaps ERR_INVALID_ARGUMENT
// would be more appropriate. // would be more appropriate.
rejectAndCleanup(-2, 'ERR_FAILED', url) rejectAndCleanup(-2, 'ERR_FAILED', url);
} };
const removeListeners = () => { const removeListeners = () => {
this.webContents.removeListener('did-finish-load', finishListener) this.webContents.removeListener('did-finish-load', finishListener);
this.webContents.removeListener('did-fail-load', failListener) this.webContents.removeListener('did-fail-load', failListener);
this.webContents.removeListener('did-start-navigation', navigationListener) this.webContents.removeListener('did-start-navigation', navigationListener);
this.webContents.removeListener('did-stop-loading', stopLoadingListener) this.webContents.removeListener('did-stop-loading', stopLoadingListener);
} };
this.webContents.on('did-finish-load', finishListener) this.webContents.on('did-finish-load', finishListener);
this.webContents.on('did-fail-load', failListener) this.webContents.on('did-fail-load', failListener);
this.webContents.on('did-start-navigation', navigationListener) this.webContents.on('did-start-navigation', navigationListener);
this.webContents.on('did-stop-loading', stopLoadingListener) this.webContents.on('did-stop-loading', stopLoadingListener);
}) });
// Add a no-op rejection handler to silence the unhandled rejection error. // Add a no-op rejection handler to silence the unhandled rejection error.
p.catch(() => {}) p.catch(() => {});
this.pendingIndex = -1 this.pendingIndex = -1;
this.webContents._loadURL(url, options) this.webContents._loadURL(url, options);
this.webContents.emit('load-url', url, options) this.webContents.emit('load-url', url, options);
return p return p;
} };
NavigationController.prototype.getURL = function () { NavigationController.prototype.getURL = function () {
if (this.currentIndex === -1) { if (this.currentIndex === -1) {
return '' return '';
} else { } else {
return this.history[this.currentIndex] return this.history[this.currentIndex];
}
} }
};
NavigationController.prototype.stop = function () { NavigationController.prototype.stop = function () {
this.pendingIndex = -1 this.pendingIndex = -1;
return this.webContents._stop() return this.webContents._stop();
} };
NavigationController.prototype.reload = function () { NavigationController.prototype.reload = function () {
this.pendingIndex = this.currentIndex this.pendingIndex = this.currentIndex;
return this.webContents._loadURL(this.getURL(), {}) return this.webContents._loadURL(this.getURL(), {});
} };
NavigationController.prototype.reloadIgnoringCache = function () { NavigationController.prototype.reloadIgnoringCache = function () {
this.pendingIndex = this.currentIndex this.pendingIndex = this.currentIndex;
return this.webContents._loadURL(this.getURL(), { return this.webContents._loadURL(this.getURL(), {
extraHeaders: 'pragma: no-cache\n', extraHeaders: 'pragma: no-cache\n',
reloadIgnoringCache: true reloadIgnoringCache: true
}) });
} };
NavigationController.prototype.canGoBack = function () { NavigationController.prototype.canGoBack = function () {
return this.getActiveIndex() > 0 return this.getActiveIndex() > 0;
} };
NavigationController.prototype.canGoForward = function () { NavigationController.prototype.canGoForward = function () {
return this.getActiveIndex() < this.history.length - 1 return this.getActiveIndex() < this.history.length - 1;
} };
NavigationController.prototype.canGoToIndex = function (index) { NavigationController.prototype.canGoToIndex = function (index) {
return index >= 0 && index < this.history.length return index >= 0 && index < this.history.length;
} };
NavigationController.prototype.canGoToOffset = function (offset) { NavigationController.prototype.canGoToOffset = function (offset) {
return this.canGoToIndex(this.currentIndex + offset) return this.canGoToIndex(this.currentIndex + offset);
} };
NavigationController.prototype.clearHistory = function () { NavigationController.prototype.clearHistory = function () {
this.history = [] this.history = [];
this.currentIndex = -1 this.currentIndex = -1;
this.pendingIndex = -1 this.pendingIndex = -1;
this.inPageIndex = -1 this.inPageIndex = -1;
} };
NavigationController.prototype.goBack = function () { NavigationController.prototype.goBack = function () {
if (!this.canGoBack()) { if (!this.canGoBack()) {
return return;
} }
this.pendingIndex = this.getActiveIndex() - 1 this.pendingIndex = this.getActiveIndex() - 1;
if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
return this.webContents._goBack() return this.webContents._goBack();
} else { } else {
return this.webContents._loadURL(this.history[this.pendingIndex], {}) return this.webContents._loadURL(this.history[this.pendingIndex], {});
}
} }
};
NavigationController.prototype.goForward = function () { NavigationController.prototype.goForward = function () {
if (!this.canGoForward()) { if (!this.canGoForward()) {
return return;
} }
this.pendingIndex = this.getActiveIndex() + 1 this.pendingIndex = this.getActiveIndex() + 1;
if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
return this.webContents._goForward() return this.webContents._goForward();
} else { } else {
return this.webContents._loadURL(this.history[this.pendingIndex], {}) return this.webContents._loadURL(this.history[this.pendingIndex], {});
}
} }
};
NavigationController.prototype.goToIndex = function (index) { NavigationController.prototype.goToIndex = function (index) {
if (!this.canGoToIndex(index)) { if (!this.canGoToIndex(index)) {
return return;
}
this.pendingIndex = index
return this.webContents._loadURL(this.history[this.pendingIndex], {})
} }
this.pendingIndex = index;
return this.webContents._loadURL(this.history[this.pendingIndex], {});
};
NavigationController.prototype.goToOffset = function (offset) { NavigationController.prototype.goToOffset = function (offset) {
if (!this.canGoToOffset(offset)) { if (!this.canGoToOffset(offset)) {
return return;
} }
const pendingIndex = this.currentIndex + offset const pendingIndex = this.currentIndex + offset;
if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) { if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) {
this.pendingIndex = pendingIndex this.pendingIndex = pendingIndex;
return this.webContents._goToOffset(offset) return this.webContents._goToOffset(offset);
} else { } else {
return this.goToIndex(pendingIndex) return this.goToIndex(pendingIndex);
}
} }
};
NavigationController.prototype.getActiveIndex = function () { NavigationController.prototype.getActiveIndex = function () {
if (this.pendingIndex === -1) { if (this.pendingIndex === -1) {
return this.currentIndex return this.currentIndex;
} else { } else {
return this.pendingIndex return this.pendingIndex;
}
} }
};
NavigationController.prototype.length = function () { NavigationController.prototype.length = function () {
return this.history.length return this.history.length;
} };
return NavigationController return NavigationController;
})() })();
module.exports = NavigationController module.exports = NavigationController;

View File

@@ -1,12 +1,12 @@
'use strict' 'use strict';
import { WebContents } from 'electron' import { WebContents } from 'electron';
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const getOwnerKey = (webContents: WebContents, contextId: string) => { const getOwnerKey = (webContents: WebContents, contextId: string) => {
return `${webContents.id}-${contextId}` return `${webContents.id}-${contextId}`;
} };
class ObjectsRegistry { class ObjectsRegistry {
private nextId: number = 0 private nextId: number = 0
@@ -23,29 +23,29 @@ class ObjectsRegistry {
// registered then the already assigned ID would be returned. // registered then the already assigned ID would be returned.
add (webContents: WebContents, contextId: string, obj: any) { add (webContents: WebContents, contextId: string, obj: any) {
// Get or assign an ID to the object. // Get or assign an ID to the object.
const id = this.saveToStorage(obj) const id = this.saveToStorage(obj);
// Add object to the set of referenced objects. // Add object to the set of referenced objects.
const ownerKey = getOwnerKey(webContents, contextId) const ownerKey = getOwnerKey(webContents, contextId);
let owner = this.owners[ownerKey] let owner = this.owners[ownerKey];
if (!owner) { if (!owner) {
owner = this.owners[ownerKey] = new Map() owner = this.owners[ownerKey] = new Map();
this.registerDeleteListener(webContents, contextId) this.registerDeleteListener(webContents, contextId);
} }
if (!owner.has(id)) { if (!owner.has(id)) {
owner.set(id, 0) owner.set(id, 0);
// Increase reference count if not referenced before. // Increase reference count if not referenced before.
this.storage[id].count++ this.storage[id].count++;
} }
owner.set(id, owner.get(id)! + 1) owner.set(id, owner.get(id)! + 1);
return id return id;
} }
// Get an object according to its ID. // Get an object according to its ID.
get (id: number) { get (id: number) {
const pointer = this.storage[id] const pointer = this.storage[id];
if (pointer != null) return pointer.object if (pointer != null) return pointer.object;
} }
// Dereference an object according to its ID. // Dereference an object according to its ID.
@@ -60,79 +60,79 @@ class ObjectsRegistry {
// For more details on why we do renderer side ref counting see // For more details on why we do renderer side ref counting see
// https://github.com/electron/electron/pull/17464 // https://github.com/electron/electron/pull/17464
remove (webContents: WebContents, contextId: string, id: number, rendererSideRefCount: number) { remove (webContents: WebContents, contextId: string, id: number, rendererSideRefCount: number) {
const ownerKey = getOwnerKey(webContents, contextId) const ownerKey = getOwnerKey(webContents, contextId);
const owner = this.owners[ownerKey] const owner = this.owners[ownerKey];
if (owner && owner.has(id)) { if (owner && owner.has(id)) {
const newRefCount = owner.get(id)! - rendererSideRefCount const newRefCount = owner.get(id)! - rendererSideRefCount;
// Only completely remove if the number of references GCed in the // Only completely remove if the number of references GCed in the
// renderer is the same as the number of references we sent them // renderer is the same as the number of references we sent them
if (newRefCount <= 0) { if (newRefCount <= 0) {
// Remove the reference in owner. // Remove the reference in owner.
owner.delete(id) owner.delete(id);
// Dereference from the storage. // Dereference from the storage.
this.dereference(id) this.dereference(id);
} else { } else {
owner.set(id, newRefCount) owner.set(id, newRefCount);
} }
} }
} }
// Clear all references to objects refrenced by the WebContents. // Clear all references to objects refrenced by the WebContents.
clear (webContents: WebContents, contextId: string) { clear (webContents: WebContents, contextId: string) {
const ownerKey = getOwnerKey(webContents, contextId) const ownerKey = getOwnerKey(webContents, contextId);
const owner = this.owners[ownerKey] const owner = this.owners[ownerKey];
if (!owner) return if (!owner) return;
for (const id of owner.keys()) this.dereference(id) for (const id of owner.keys()) this.dereference(id);
delete this.owners[ownerKey] delete this.owners[ownerKey];
} }
// Private: Saves the object into storage and assigns an ID for it. // Private: Saves the object into storage and assigns an ID for it.
saveToStorage (object: any) { saveToStorage (object: any) {
let id: number = v8Util.getHiddenValue(object, 'atomId') let id: number = v8Util.getHiddenValue(object, 'atomId');
if (!id) { if (!id) {
id = ++this.nextId id = ++this.nextId;
this.storage[id] = { this.storage[id] = {
count: 0, count: 0,
object: object object: object
};
v8Util.setHiddenValue(object, 'atomId', id);
} }
v8Util.setHiddenValue(object, 'atomId', id) return id;
}
return id
} }
// Private: Dereference the object from store. // Private: Dereference the object from store.
dereference (id: number) { dereference (id: number) {
const pointer = this.storage[id] const pointer = this.storage[id];
if (pointer == null) { if (pointer == null) {
return return;
} }
pointer.count -= 1 pointer.count -= 1;
if (pointer.count === 0) { if (pointer.count === 0) {
v8Util.deleteHiddenValue(pointer.object, 'atomId') v8Util.deleteHiddenValue(pointer.object, 'atomId');
delete this.storage[id] delete this.storage[id];
} }
} }
// Private: Clear the storage when renderer process is destroyed. // Private: Clear the storage when renderer process is destroyed.
registerDeleteListener (webContents: WebContents, contextId: string) { registerDeleteListener (webContents: WebContents, contextId: string) {
// contextId => ${processHostId}-${contextCount} // contextId => ${processHostId}-${contextCount}
const processHostId = contextId.split('-')[0] const processHostId = contextId.split('-')[0];
const listener = (_: any, deletedProcessHostId: string) => { const listener = (_: any, deletedProcessHostId: string) => {
if (deletedProcessHostId && if (deletedProcessHostId &&
deletedProcessHostId.toString() === processHostId) { deletedProcessHostId.toString() === processHostId) {
webContents.removeListener('render-view-deleted' as any, listener) webContents.removeListener('render-view-deleted' as any, listener);
this.clear(webContents, contextId) this.clear(webContents, contextId);
}
} }
};
// Note that the "render-view-deleted" event may not be emitted on time when // Note that the "render-view-deleted" event may not be emitted on time when
// the renderer process get destroyed because of navigation, we rely on the // the renderer process get destroyed because of navigation, we rely on the
// renderer process to send "ELECTRON_BROWSER_CONTEXT_RELEASE" message to // renderer process to send "ELECTRON_BROWSER_CONTEXT_RELEASE" message to
// guard this situation. // guard this situation.
webContents.on('render-view-deleted' as any, listener) webContents.on('render-view-deleted' as any, listener);
} }
} }
export default new ObjectsRegistry() export default new ObjectsRegistry();

View File

@@ -1,29 +1,29 @@
'use strict' 'use strict';
import * as electron from 'electron' import * as electron from 'electron';
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
import objectsRegistry from './objects-registry' import objectsRegistry from './objects-registry';
import { ipcMainInternal } from '../ipc-main-internal' import { ipcMainInternal } from '../ipc-main-internal';
import { isPromise, isSerializableObject } from '@electron/internal/common/type-utils' import { isPromise, isSerializableObject } from '@electron/internal/common/type-utils';
const v8Util = process.electronBinding('v8_util') const v8Util = process.electronBinding('v8_util');
const eventBinding = process.electronBinding('event') const eventBinding = process.electronBinding('event');
const features = process.electronBinding('features') const features = process.electronBinding('features');
if (!features.isRemoteModuleEnabled()) { if (!features.isRemoteModuleEnabled()) {
throw new Error('remote module is disabled') throw new Error('remote module is disabled');
} }
const hasProp = {}.hasOwnProperty const hasProp = {}.hasOwnProperty;
// The internal properties of Function. // The internal properties of Function.
const FUNCTION_PROPERTIES = [ const FUNCTION_PROPERTIES = [
'length', 'name', 'arguments', 'caller', 'prototype' 'length', 'name', 'arguments', 'caller', 'prototype'
] ];
// The remote functions in renderer processes. // The remote functions in renderer processes.
// id => Function // id => Function
const rendererFunctions = v8Util.createDoubleIDWeakMap<(...args: any[]) => void>() const rendererFunctions = v8Util.createDoubleIDWeakMap<(...args: any[]) => void>();
type ObjectMember = { type ObjectMember = {
name: string, name: string,
@@ -35,28 +35,28 @@ type ObjectMember = {
// Return the description of object's members: // Return the description of object's members:
const getObjectMembers = function (object: any): ObjectMember[] { const getObjectMembers = function (object: any): ObjectMember[] {
let names = Object.getOwnPropertyNames(object) let names = Object.getOwnPropertyNames(object);
// For Function, we should not override following properties even though they // For Function, we should not override following properties even though they
// are "own" properties. // are "own" properties.
if (typeof object === 'function') { if (typeof object === 'function') {
names = names.filter((name) => { names = names.filter((name) => {
return !FUNCTION_PROPERTIES.includes(name) return !FUNCTION_PROPERTIES.includes(name);
}) });
} }
// Map properties to descriptors. // Map properties to descriptors.
return names.map((name) => { return names.map((name) => {
const descriptor = Object.getOwnPropertyDescriptor(object, name)! const descriptor = Object.getOwnPropertyDescriptor(object, name)!;
let type: ObjectMember['type'] let type: ObjectMember['type'];
let writable = false let writable = false;
if (descriptor.get === undefined && typeof object[name] === 'function') { if (descriptor.get === undefined && typeof object[name] === 'function') {
type = 'method' type = 'method';
} else { } else {
if (descriptor.set || descriptor.writable) writable = true if (descriptor.set || descriptor.writable) writable = true;
type = 'get' type = 'get';
}
return { name, enumerable: descriptor.enumerable, writable, type }
})
} }
return { name, enumerable: descriptor.enumerable, writable, type };
});
};
type ObjProtoDescriptor = { type ObjProtoDescriptor = {
members: ObjectMember[], members: ObjectMember[],
@@ -65,13 +65,13 @@ type ObjProtoDescriptor = {
// Return the description of object's prototype. // Return the description of object's prototype.
const getObjectPrototype = function (object: any): ObjProtoDescriptor { const getObjectPrototype = function (object: any): ObjProtoDescriptor {
const proto = Object.getPrototypeOf(object) const proto = Object.getPrototypeOf(object);
if (proto === null || proto === Object.prototype) return null if (proto === null || proto === Object.prototype) return null;
return { return {
members: getObjectMembers(proto), members: getObjectMembers(proto),
proto: getObjectPrototype(proto) proto: getObjectPrototype(proto)
} };
} };
type MetaType = { type MetaType = {
type: 'number', type: 'number',
@@ -118,25 +118,25 @@ type MetaType = {
// Convert a real value into meta data. // Convert a real value into meta data.
const valueToMeta = function (sender: electron.WebContents, contextId: string, value: any, optimizeSimpleObject = false): MetaType { const valueToMeta = function (sender: electron.WebContents, contextId: string, value: any, optimizeSimpleObject = false): MetaType {
// Determine the type of value. // Determine the type of value.
let type: MetaType['type'] = typeof value let type: MetaType['type'] = typeof value;
if (type === 'object') { if (type === 'object') {
// Recognize certain types of objects. // Recognize certain types of objects.
if (value instanceof Buffer) { if (value instanceof Buffer) {
type = 'buffer' type = 'buffer';
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
type = 'array' type = 'array';
} else if (value instanceof Error) { } else if (value instanceof Error) {
type = 'error' type = 'error';
} else if (isSerializableObject(value)) { } else if (isSerializableObject(value)) {
type = 'value' type = 'value';
} else if (isPromise(value)) { } else if (isPromise(value)) {
type = 'promise' type = 'promise';
} else if (hasProp.call(value, 'callee') && value.length != null) { } else if (hasProp.call(value, 'callee') && value.length != null) {
// Treat the arguments object as array. // Treat the arguments object as array.
type = 'array' type = 'array';
} else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) { } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
// Treat simple objects as value. // Treat simple objects as value.
type = 'value' type = 'value';
} }
} }
@@ -145,7 +145,7 @@ const valueToMeta = function (sender: electron.WebContents, contextId: string, v
return { return {
type, type,
members: value.map((el: any) => valueToMeta(sender, contextId, el, optimizeSimpleObject)) members: value.map((el: any) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
} };
} else if (type === 'object' || type === 'function') { } else if (type === 'object' || type === 'function') {
return { return {
type, type,
@@ -156,20 +156,20 @@ const valueToMeta = function (sender: electron.WebContents, contextId: string, v
id: objectsRegistry.add(sender, contextId, value), id: objectsRegistry.add(sender, contextId, value),
members: getObjectMembers(value), members: getObjectMembers(value),
proto: getObjectPrototype(value) proto: getObjectPrototype(value)
} };
} else if (type === 'buffer') { } else if (type === 'buffer') {
return { type, value } return { type, value };
} else if (type === 'promise') { } else if (type === 'promise') {
// Add default handler to prevent unhandled rejections in main process // Add default handler to prevent unhandled rejections in main process
// Instead they should appear in the renderer process // Instead they should appear in the renderer process
value.then(function () {}, function () {}) value.then(function () {}, function () {});
return { return {
type, type,
then: valueToMeta(sender, contextId, function (onFulfilled: Function, onRejected: Function) { then: valueToMeta(sender, contextId, function (onFulfilled: Function, onRejected: Function) {
value.then(onFulfilled, onRejected) value.then(onFulfilled, onRejected);
}) })
} };
} else if (type === 'error') { } else if (type === 'error') {
return { return {
type, type,
@@ -178,42 +178,42 @@ const valueToMeta = function (sender: electron.WebContents, contextId: string, v
name, name,
value: valueToMeta(sender, contextId, value[name]) value: valueToMeta(sender, contextId, value[name])
})) }))
} };
} else { } else {
return { return {
type: 'value', type: 'value',
value value
};
} }
} };
}
const throwRPCError = function (message: string) { const throwRPCError = function (message: string) {
const error = new Error(message) as Error & {code: string, errno: number} const error = new Error(message) as Error & {code: string, errno: number};
error.code = 'EBADRPC' error.code = 'EBADRPC';
error.errno = -72 error.errno = -72;
throw error throw error;
} };
const removeRemoteListenersAndLogWarning = (sender: any, callIntoRenderer: (...args: any[]) => void) => { const removeRemoteListenersAndLogWarning = (sender: any, callIntoRenderer: (...args: any[]) => void) => {
const location = v8Util.getHiddenValue(callIntoRenderer, 'location') const location = v8Util.getHiddenValue(callIntoRenderer, 'location');
let message = `Attempting to call a function in a renderer window that has been closed or released.` + let message = `Attempting to call a function in a renderer window that has been closed or released.` +
`\nFunction provided here: ${location}` `\nFunction provided here: ${location}`;
if (sender instanceof EventEmitter) { if (sender instanceof EventEmitter) {
const remoteEvents = sender.eventNames().filter((eventName) => { const remoteEvents = sender.eventNames().filter((eventName) => {
return sender.listeners(eventName).includes(callIntoRenderer) return sender.listeners(eventName).includes(callIntoRenderer);
}) });
if (remoteEvents.length > 0) { if (remoteEvents.length > 0) {
message += `\nRemote event names: ${remoteEvents.join(', ')}` message += `\nRemote event names: ${remoteEvents.join(', ')}`;
remoteEvents.forEach((eventName) => { remoteEvents.forEach((eventName) => {
sender.removeListener(eventName as any, callIntoRenderer) sender.removeListener(eventName as any, callIntoRenderer);
}) });
} }
} }
console.warn(message) console.warn(message);
} };
type MetaTypeFromRenderer = { type MetaTypeFromRenderer = {
type: 'value', type: 'value',
@@ -248,300 +248,300 @@ const fakeConstructor = (constructor: Function, name: string) =>
new Proxy(Object, { new Proxy(Object, {
get (target, prop, receiver) { get (target, prop, receiver) {
if (prop === 'name') { if (prop === 'name') {
return name return name;
} else { } else {
return Reflect.get(target, prop, receiver) return Reflect.get(target, prop, receiver);
} }
} }
}) });
// Convert array of meta data from renderer into array of real values. // Convert array of meta data from renderer into array of real values.
const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) { const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) {
const metaToValue = function (meta: MetaTypeFromRenderer): any { const metaToValue = function (meta: MetaTypeFromRenderer): any {
switch (meta.type) { switch (meta.type) {
case 'value': case 'value':
return meta.value return meta.value;
case 'remote-object': case 'remote-object':
return objectsRegistry.get(meta.id) return objectsRegistry.get(meta.id);
case 'array': case 'array':
return unwrapArgs(sender, frameId, contextId, meta.value) return unwrapArgs(sender, frameId, contextId, meta.value);
case 'buffer': case 'buffer':
return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength) return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength);
case 'promise': case 'promise':
return Promise.resolve({ return Promise.resolve({
then: metaToValue(meta.then) then: metaToValue(meta.then)
}) });
case 'object': { case 'object': {
const ret: any = meta.name !== 'Object' ? Object.create({ const ret: any = meta.name !== 'Object' ? Object.create({
constructor: fakeConstructor(Object, meta.name) constructor: fakeConstructor(Object, meta.name)
}) : {} }) : {};
for (const { name, value } of meta.members) { for (const { name, value } of meta.members) {
ret[name] = metaToValue(value) ret[name] = metaToValue(value);
} }
return ret return ret;
} }
case 'function-with-return-value': case 'function-with-return-value':
const returnValue = metaToValue(meta.value) const returnValue = metaToValue(meta.value);
return function () { return function () {
return returnValue return returnValue;
} };
case 'function': { case 'function': {
// Merge contextId and meta.id, since meta.id can be the same in // Merge contextId and meta.id, since meta.id can be the same in
// different webContents. // different webContents.
const objectId: [string, number] = [contextId, meta.id] const objectId: [string, number] = [contextId, meta.id];
// Cache the callbacks in renderer. // Cache the callbacks in renderer.
if (rendererFunctions.has(objectId)) { if (rendererFunctions.has(objectId)) {
return rendererFunctions.get(objectId) return rendererFunctions.get(objectId);
} }
const callIntoRenderer = function (this: any, ...args: any[]) { const callIntoRenderer = function (this: any, ...args: any[]) {
let succeed = false let succeed = false;
if (!sender.isDestroyed()) { if (!sender.isDestroyed()) {
succeed = (sender as any)._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args)) succeed = (sender as any)._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args));
} }
if (!succeed) { if (!succeed) {
removeRemoteListenersAndLogWarning(this, callIntoRenderer) removeRemoteListenersAndLogWarning(this, callIntoRenderer);
} }
} };
v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location) v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location);
Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }) Object.defineProperty(callIntoRenderer, 'length', { value: meta.length });
v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender) v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender);
rendererFunctions.set(objectId, callIntoRenderer) rendererFunctions.set(objectId, callIntoRenderer);
return callIntoRenderer return callIntoRenderer;
} }
default: default:
throw new TypeError(`Unknown type: ${(meta as any).type}`) throw new TypeError(`Unknown type: ${(meta as any).type}`);
}
}
return args.map(metaToValue)
} }
};
return args.map(metaToValue);
};
const isRemoteModuleEnabledImpl = function (contents: electron.WebContents) { const isRemoteModuleEnabledImpl = function (contents: electron.WebContents) {
const webPreferences = (contents as any).getLastWebPreferences() || {} const webPreferences = (contents as any).getLastWebPreferences() || {};
return webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : true return webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : true;
} };
const isRemoteModuleEnabledCache = new WeakMap() const isRemoteModuleEnabledCache = new WeakMap();
const isRemoteModuleEnabled = function (contents: electron.WebContents) { const isRemoteModuleEnabled = function (contents: electron.WebContents) {
if (!isRemoteModuleEnabledCache.has(contents)) { if (!isRemoteModuleEnabledCache.has(contents)) {
isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents)) isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents));
} }
return isRemoteModuleEnabledCache.get(contents) return isRemoteModuleEnabledCache.get(contents);
} };
const handleRemoteCommand = function (channel: string, handler: (event: ElectronInternal.IpcMainInternalEvent, contextId: string, ...args: any[]) => void) { const handleRemoteCommand = function (channel: string, handler: (event: ElectronInternal.IpcMainInternalEvent, contextId: string, ...args: any[]) => void) {
ipcMainInternal.on(channel, (event, contextId: string, ...args: any[]) => { ipcMainInternal.on(channel, (event, contextId: string, ...args: any[]) => {
let returnValue let returnValue;
if (!isRemoteModuleEnabled(event.sender)) { if (!isRemoteModuleEnabled(event.sender)) {
event.returnValue = null event.returnValue = null;
return return;
} }
try { try {
returnValue = handler(event, contextId, ...args) returnValue = handler(event, contextId, ...args);
} catch (error) { } catch (error) {
returnValue = { returnValue = {
type: 'exception', type: 'exception',
value: valueToMeta(event.sender, contextId, error) value: valueToMeta(event.sender, contextId, error)
} };
} }
if (returnValue !== undefined) { if (returnValue !== undefined) {
event.returnValue = returnValue event.returnValue = returnValue;
}
})
} }
});
};
const emitCustomEvent = function (contents: electron.WebContents, eventName: string, ...args: any[]) { const emitCustomEvent = function (contents: electron.WebContents, eventName: string, ...args: any[]) {
const event = eventBinding.createWithSender(contents) const event = eventBinding.createWithSender(contents);
electron.app.emit(eventName, event, contents, ...args) electron.app.emit(eventName, event, contents, ...args);
contents.emit(eventName, event, ...args) contents.emit(eventName, event, ...args);
return event return event;
} };
const logStack = function (contents: electron.WebContents, code: string, stack: string | undefined) { const logStack = function (contents: electron.WebContents, code: string, stack: string | undefined) {
if (stack) { if (stack) {
console.warn(`WebContents (${contents.id}): ${code}`, stack) console.warn(`WebContents (${contents.id}): ${code}`, stack);
}
} }
};
handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) { handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
const objectId: [string, number] = [passedContextId, id] const objectId: [string, number] = [passedContextId, id];
if (!rendererFunctions.has(objectId)) { if (!rendererFunctions.has(objectId)) {
// Do nothing if the error has already been reported before. // Do nothing if the error has already been reported before.
return return;
} }
removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId)!) removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId)!);
}) });
handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName, stack) { handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName, stack) {
logStack(event.sender, `remote.require('${moduleName}')`, stack) logStack(event.sender, `remote.require('${moduleName}')`, stack);
const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName) const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName);
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.require('${moduleName}')`) throw new Error(`Blocked remote.require('${moduleName}')`);
} else { } else {
customEvent.returnValue = process.mainModule!.require(moduleName) customEvent.returnValue = process.mainModule!.require(moduleName);
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName, stack) { handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName, stack) {
logStack(event.sender, `remote.getBuiltin('${moduleName}')`, stack) logStack(event.sender, `remote.getBuiltin('${moduleName}')`, stack);
const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName) const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName);
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.getBuiltin('${moduleName}')`) throw new Error(`Blocked remote.getBuiltin('${moduleName}')`);
} else { } else {
customEvent.returnValue = (electron as any)[moduleName] customEvent.returnValue = (electron as any)[moduleName];
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName, stack) { handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName, stack) {
logStack(event.sender, `remote.getGlobal('${globalName}')`, stack) logStack(event.sender, `remote.getGlobal('${globalName}')`, stack);
const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName) const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName);
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error(`Blocked remote.getGlobal('${globalName}')`) throw new Error(`Blocked remote.getGlobal('${globalName}')`);
} else { } else {
customEvent.returnValue = (global as any)[globalName] customEvent.returnValue = (global as any)[globalName];
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId, stack) { handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId, stack) {
logStack(event.sender, 'remote.getCurrentWindow()', stack) logStack(event.sender, 'remote.getCurrentWindow()', stack);
const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window') const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window');
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error('Blocked remote.getCurrentWindow()') throw new Error('Blocked remote.getCurrentWindow()');
} else { } else {
customEvent.returnValue = event.sender.getOwnerBrowserWindow() customEvent.returnValue = event.sender.getOwnerBrowserWindow();
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId, stack) { handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId, stack) {
logStack(event.sender, 'remote.getCurrentWebContents()', stack) logStack(event.sender, 'remote.getCurrentWebContents()', stack);
const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents') const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents');
if (customEvent.returnValue === undefined) { if (customEvent.returnValue === undefined) {
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
throw new Error('Blocked remote.getCurrentWebContents()') throw new Error('Blocked remote.getCurrentWebContents()');
} else { } else {
customEvent.returnValue = event.sender customEvent.returnValue = event.sender;
} }
} }
return valueToMeta(event.sender, contextId, customEvent.returnValue) return valueToMeta(event.sender, contextId, customEvent.returnValue);
}) });
handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) { handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const constructor = objectsRegistry.get(id) const constructor = objectsRegistry.get(id);
if (constructor == null) { if (constructor == null) {
throwRPCError(`Cannot call constructor on missing remote object ${id}`) throwRPCError(`Cannot call constructor on missing remote object ${id}`);
} }
return valueToMeta(event.sender, contextId, new constructor(...args)) return valueToMeta(event.sender, contextId, new constructor(...args));
}) });
handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) { handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const func = objectsRegistry.get(id) const func = objectsRegistry.get(id);
if (func == null) { if (func == null) {
throwRPCError(`Cannot call function on missing remote object ${id}`) throwRPCError(`Cannot call function on missing remote object ${id}`);
} }
try { try {
return valueToMeta(event.sender, contextId, func(...args), true) return valueToMeta(event.sender, contextId, func(...args), true);
} catch (error) { } catch (error) {
const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`); const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`);
(err as any).cause = error (err as any).cause = error;
throw err throw err;
} }
}) });
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) { handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const object = objectsRegistry.get(id) const object = objectsRegistry.get(id);
if (object == null) { if (object == null) {
throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`) throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`);
} }
return valueToMeta(event.sender, contextId, new object[method](...args)) return valueToMeta(event.sender, contextId, new object[method](...args));
}) });
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) { handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const object = objectsRegistry.get(id) const object = objectsRegistry.get(id);
if (object == null) { if (object == null) {
throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`) throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`);
} }
try { try {
return valueToMeta(event.sender, contextId, object[method](...args), true) return valueToMeta(event.sender, contextId, object[method](...args), true);
} catch (error) { } catch (error) {
const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`); const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`);
(err as any).cause = error (err as any).cause = error;
throw err throw err;
} }
}) });
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) { handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
args = unwrapArgs(event.sender, event.frameId, contextId, args) args = unwrapArgs(event.sender, event.frameId, contextId, args);
const obj = objectsRegistry.get(id) const obj = objectsRegistry.get(id);
if (obj == null) { if (obj == null) {
throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`) throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`);
} }
obj[name] = args[0] obj[name] = args[0];
return null return null;
}) });
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) { handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
const obj = objectsRegistry.get(id) const obj = objectsRegistry.get(id);
if (obj == null) { if (obj == null) {
throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`) throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`);
} }
return valueToMeta(event.sender, contextId, obj[name]) return valueToMeta(event.sender, contextId, obj[name]);
}) });
handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) { handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) {
objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount) objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount);
}) });
handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => { handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
objectsRegistry.clear(event.sender, contextId) objectsRegistry.clear(event.sender, contextId);
}) });
module.exports = { module.exports = {
isRemoteModuleEnabled isRemoteModuleEnabled
} };

View File

@@ -1,118 +1,118 @@
'use strict' 'use strict';
const electron = require('electron') const electron = require('electron');
const fs = require('fs') const fs = require('fs');
const eventBinding = process.electronBinding('event') const eventBinding = process.electronBinding('event');
const clipboard = process.electronBinding('clipboard') const clipboard = process.electronBinding('clipboard');
const features = process.electronBinding('features') const features = process.electronBinding('features');
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init') const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const guestViewManager = require('@electron/internal/browser/guest-view-manager') const guestViewManager = require('@electron/internal/browser/guest-view-manager');
const typeUtils = require('@electron/internal/common/type-utils') const typeUtils = require('@electron/internal/common/type-utils');
const emitCustomEvent = function (contents, eventName, ...args) { const emitCustomEvent = function (contents, eventName, ...args) {
const event = eventBinding.createWithSender(contents) const event = eventBinding.createWithSender(contents);
electron.app.emit(eventName, event, contents, ...args) electron.app.emit(eventName, event, contents, ...args);
contents.emit(eventName, event, ...args) contents.emit(eventName, event, ...args);
return event return event;
} };
const logStack = function (contents, code, stack) { const logStack = function (contents, code, stack) {
if (stack) { if (stack) {
console.warn(`WebContents (${contents.id}): ${code}`, stack) console.warn(`WebContents (${contents.id}): ${code}`, stack);
}
} }
};
// Implements window.close() // Implements window.close()
ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
const window = event.sender.getOwnerBrowserWindow() const window = event.sender.getOwnerBrowserWindow();
if (window) { if (window) {
window.close() window.close();
} }
event.returnValue = null event.returnValue = null;
}) });
ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_INIT', function (event, options) {
return crashReporterInit(options) return crashReporterInit(options);
}) });
ipcMainInternal.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) { ipcMainInternal.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) {
return event.sender.getLastWebPreferences() return event.sender.getLastWebPreferences();
}) });
// Methods not listed in this set are called directly in the renderer process. // Methods not listed in this set are called directly in the renderer process.
const allowedClipboardMethods = (() => { const allowedClipboardMethods = (() => {
switch (process.platform) { switch (process.platform) {
case 'darwin': case 'darwin':
return new Set(['readFindText', 'writeFindText']) return new Set(['readFindText', 'writeFindText']);
case 'linux': case 'linux':
return new Set(Object.keys(clipboard)) return new Set(Object.keys(clipboard));
default: default:
return new Set() return new Set();
} }
})() })();
ipcMainUtils.handleSync('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...args) { ipcMainUtils.handleSync('ELECTRON_BROWSER_CLIPBOARD_SYNC', function (event, method, ...args) {
if (!allowedClipboardMethods.has(method)) { if (!allowedClipboardMethods.has(method)) {
throw new Error(`Invalid method: ${method}`) throw new Error(`Invalid method: ${method}`);
} }
return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args))) return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args)));
}) });
if (features.isDesktopCapturerEnabled()) { if (features.isDesktopCapturerEnabled()) {
const desktopCapturer = require('@electron/internal/browser/desktop-capturer') const desktopCapturer = require('@electron/internal/browser/desktop-capturer');
ipcMainInternal.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, options, stack) { ipcMainInternal.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, options, stack) {
logStack(event.sender, 'desktopCapturer.getSources()', stack) logStack(event.sender, 'desktopCapturer.getSources()', stack);
const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources') const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources');
if (customEvent.defaultPrevented) { if (customEvent.defaultPrevented) {
console.error('Blocked desktopCapturer.getSources()') console.error('Blocked desktopCapturer.getSources()');
return [] return [];
} }
return desktopCapturer.getSources(event, options) return desktopCapturer.getSources(event, options);
}) });
} }
const isRemoteModuleEnabled = features.isRemoteModuleEnabled() const isRemoteModuleEnabled = features.isRemoteModuleEnabled()
? require('@electron/internal/browser/remote/server').isRemoteModuleEnabled ? require('@electron/internal/browser/remote/server').isRemoteModuleEnabled
: () => false : () => false;
const getPreloadScript = async function (preloadPath) { const getPreloadScript = async function (preloadPath) {
let preloadSrc = null let preloadSrc = null;
let preloadError = null let preloadError = null;
try { try {
preloadSrc = (await fs.promises.readFile(preloadPath)).toString() preloadSrc = (await fs.promises.readFile(preloadPath)).toString();
} catch (error) { } catch (error) {
preloadError = error preloadError = error;
}
return { preloadPath, preloadSrc, preloadError }
} }
return { preloadPath, preloadSrc, preloadError };
};
if (features.isExtensionsEnabled()) { if (features.isExtensionsEnabled()) {
ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => []) ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => []);
} else { } else {
const { getContentScripts } = require('@electron/internal/browser/chrome-extension') const { getContentScripts } = require('@electron/internal/browser/chrome-extension');
ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts()) ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts());
} }
ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) { ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) {
const preloadPaths = event.sender._getPreloadPaths() const preloadPaths = event.sender._getPreloadPaths();
let contentScripts = [] let contentScripts = [];
if (!features.isExtensionsEnabled()) { if (!features.isExtensionsEnabled()) {
const { getContentScripts } = require('@electron/internal/browser/chrome-extension') const { getContentScripts } = require('@electron/internal/browser/chrome-extension');
contentScripts = getContentScripts() contentScripts = getContentScripts();
} }
const webPreferences = event.sender.getLastWebPreferences() || {} const webPreferences = event.sender.getLastWebPreferences() || {};
return { return {
contentScripts, contentScripts,
@@ -129,9 +129,9 @@ ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event)
versions: process.versions, versions: process.versions,
execPath: process.helperExecPath execPath: process.helperExecPath
} }
} };
}) });
ipcMainInternal.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) { ipcMainInternal.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) {
event.sender.emit('preload-error', event, preloadPath, error) event.sender.emit('preload-error', event, preloadPath, error);
}) });

View File

@@ -1,4 +1,4 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events';
/** /**
* Creates a lazy instance of modules that can't be required before the * Creates a lazy instance of modules that can't be required before the
@@ -16,23 +16,23 @@ export function createLazyInstance (
holder: Object, holder: Object,
isEventEmitter: Boolean isEventEmitter: Boolean
) { ) {
let lazyModule: Object let lazyModule: Object;
const module: any = {} const module: any = {};
for (const method in (holder as any).prototype) { for (const method in (holder as any).prototype) { // eslint-disable-line guard-for-in
module[method] = (...args: any) => { module[method] = (...args: any) => {
// create new instance of module at runtime if none exists // create new instance of module at runtime if none exists
if (!lazyModule) { if (!lazyModule) {
lazyModule = creator() lazyModule = creator();
if (isEventEmitter) EventEmitter.call(lazyModule as any) if (isEventEmitter) EventEmitter.call(lazyModule as any);
} }
// check for properties on the prototype chain that aren't functions // check for properties on the prototype chain that aren't functions
if (typeof (lazyModule as any)[method] !== 'function') { if (typeof (lazyModule as any)[method] !== 'function') {
return (lazyModule as any)[method] return (lazyModule as any)[method];
} }
return (lazyModule as any)[method](...args) return (lazyModule as any)[method](...args);
};
} }
} return module;
return module
} }

View File

@@ -1,29 +1,29 @@
'use strict' 'use strict';
const clipboard = process.electronBinding('clipboard') const clipboard = process.electronBinding('clipboard');
if (process.type === 'renderer') { if (process.type === 'renderer') {
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
const typeUtils = require('@electron/internal/common/type-utils') const typeUtils = require('@electron/internal/common/type-utils');
const makeRemoteMethod = function (method) { const makeRemoteMethod = function (method) {
return (...args) => { return (...args) => {
args = typeUtils.serialize(args) args = typeUtils.serialize(args);
const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...args) const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD_SYNC', method, ...args);
return typeUtils.deserialize(result) return typeUtils.deserialize(result);
} };
} };
if (process.platform === 'linux') { if (process.platform === 'linux') {
// On Linux we could not access clipboard in renderer process. // On Linux we could not access clipboard in renderer process.
for (const method of Object.keys(clipboard)) { for (const method of Object.keys(clipboard)) {
clipboard[method] = makeRemoteMethod(method) clipboard[method] = makeRemoteMethod(method);
} }
} else if (process.platform === 'darwin') { } else if (process.platform === 'darwin') {
// Read/write to find pasteboard over IPC since only main process is notified of changes // Read/write to find pasteboard over IPC since only main process is notified of changes
clipboard.readFindText = makeRemoteMethod('readFindText') clipboard.readFindText = makeRemoteMethod('readFindText');
clipboard.writeFindText = makeRemoteMethod('writeFindText') clipboard.writeFindText = makeRemoteMethod('writeFindText');
} }
} }
module.exports = clipboard module.exports = clipboard;

View File

@@ -1,95 +1,95 @@
let deprecationHandler: ElectronInternal.DeprecationHandler | null = null let deprecationHandler: ElectronInternal.DeprecationHandler | null = null;
function warnOnce (oldName: string, newName?: string) { function warnOnce (oldName: string, newName?: string) {
let warned = false let warned = false;
const msg = newName const msg = newName
? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.` ? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
: `'${oldName}' is deprecated and will be removed.` : `'${oldName}' is deprecated and will be removed.`;
return () => { return () => {
if (!warned && !process.noDeprecation) { if (!warned && !process.noDeprecation) {
warned = true warned = true;
deprecate.log(msg) deprecate.log(msg);
}
} }
};
} }
const deprecate: ElectronInternal.DeprecationUtil = { const deprecate: ElectronInternal.DeprecationUtil = {
warnOnce, warnOnce,
setHandler: (handler) => { deprecationHandler = handler }, setHandler: (handler) => { deprecationHandler = handler; },
getHandler: () => deprecationHandler, getHandler: () => deprecationHandler,
warn: (oldName, newName) => { warn: (oldName, newName) => {
if (!process.noDeprecation) { if (!process.noDeprecation) {
deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`) deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`);
} }
}, },
log: (message) => { log: (message) => {
if (typeof deprecationHandler === 'function') { if (typeof deprecationHandler === 'function') {
deprecationHandler(message) deprecationHandler(message);
} else if (process.throwDeprecation) { } else if (process.throwDeprecation) {
throw new Error(message) throw new Error(message);
} else if (process.traceDeprecation) { } else if (process.traceDeprecation) {
return console.trace(message) return console.trace(message);
} else { } else {
return console.warn(`(electron) ${message}`) return console.warn(`(electron) ${message}`);
} }
}, },
// remove a function with no replacement // remove a function with no replacement
removeFunction: (fn, removedName) => { removeFunction: (fn, removedName) => {
if (!fn) { throw Error(`'${removedName} function' is invalid or does not exist.`) } if (!fn) { throw Error(`'${removedName} function' is invalid or does not exist.`); }
// wrap the deprecated function to warn user // wrap the deprecated function to warn user
const warn = warnOnce(`${fn.name} function`) const warn = warnOnce(`${fn.name} function`);
return function (this: any) { return function (this: any) {
warn() warn();
fn.apply(this, arguments) fn.apply(this, arguments);
} };
}, },
// change the name of a function // change the name of a function
renameFunction: (fn, newName) => { renameFunction: (fn, newName) => {
const warn = warnOnce(`${fn.name} function`, `${newName} function`) const warn = warnOnce(`${fn.name} function`, `${newName} function`);
return function (this: any) { return function (this: any) {
warn() warn();
return fn.apply(this, arguments) return fn.apply(this, arguments);
} };
}, },
moveAPI: (fn: Function, oldUsage: string, newUsage: string) => { moveAPI: (fn: Function, oldUsage: string, newUsage: string) => {
const warn = warnOnce(oldUsage, newUsage) const warn = warnOnce(oldUsage, newUsage);
return function (this: any) { return function (this: any) {
warn() warn();
return fn.apply(this, arguments) return fn.apply(this, arguments);
} };
}, },
// change the name of an event // change the name of an event
event: (emitter, oldName, newName) => { event: (emitter, oldName, newName) => {
const warn = newName.startsWith('-') /* internal event */ const warn = newName.startsWith('-') /* internal event */
? warnOnce(`${oldName} event`) ? warnOnce(`${oldName} event`)
: warnOnce(`${oldName} event`, `${newName} event`) : warnOnce(`${oldName} event`, `${newName} event`);
return emitter.on(newName, function (this: NodeJS.EventEmitter, ...args) { return emitter.on(newName, function (this: NodeJS.EventEmitter, ...args) {
if (this.listenerCount(oldName) !== 0) { if (this.listenerCount(oldName) !== 0) {
warn() warn();
this.emit(oldName, ...args) this.emit(oldName, ...args);
} }
}) });
}, },
// deprecate a getter/setter function pair in favor of a property // deprecate a getter/setter function pair in favor of a property
fnToProperty: (prototype: any, prop: string, getter: string, setter?: string) => { fnToProperty: (prototype: any, prop: string, getter: string, setter?: string) => {
const withWarnOnce = function (obj: any, key: any, oldName: string, newName: string) { const withWarnOnce = function (obj: any, key: any, oldName: string, newName: string) {
const warn = warnOnce(oldName, newName) const warn = warnOnce(oldName, newName);
const method = obj[key] const method = obj[key];
return function (this: any, ...args: any) { return function (this: any, ...args: any) {
warn() warn();
return method.apply(this, args) return method.apply(this, args);
} };
} };
prototype[getter.substr(1)] = withWarnOnce(prototype, getter, `${getter.substr(1)} function`, `${prop} property`) prototype[getter.substr(1)] = withWarnOnce(prototype, getter, `${getter.substr(1)} function`, `${prop} property`);
if (setter) { if (setter) {
prototype[setter.substr(1)] = withWarnOnce(prototype, setter, `${setter.substr(1)} function`, `${prop} property`) prototype[setter.substr(1)] = withWarnOnce(prototype, setter, `${setter.substr(1)} function`, `${prop} property`);
} }
}, },
@@ -97,49 +97,49 @@ const deprecate: ElectronInternal.DeprecationUtil = {
removeProperty: (o, removedName) => { removeProperty: (o, removedName) => {
// if the property's already been removed, warn about it // if the property's already been removed, warn about it
if (!(removedName in o)) { if (!(removedName in o)) {
deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`) deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`);
} }
// wrap the deprecated property in an accessor to warn // wrap the deprecated property in an accessor to warn
const warn = warnOnce(removedName) const warn = warnOnce(removedName);
let val = o[removedName] let val = o[removedName];
return Object.defineProperty(o, removedName, { return Object.defineProperty(o, removedName, {
configurable: true, configurable: true,
get: () => { get: () => {
warn() warn();
return val return val;
}, },
set: newVal => { set: newVal => {
warn() warn();
val = newVal val = newVal;
} }
}) });
}, },
// change the name of a property // change the name of a property
renameProperty: (o, oldName, newName) => { renameProperty: (o, oldName, newName) => {
const warn = warnOnce(oldName, newName) const warn = warnOnce(oldName, newName);
// if the new property isn't there yet, // if the new property isn't there yet,
// inject it and warn about it // inject it and warn about it
if ((oldName in o) && !(newName in o)) { if ((oldName in o) && !(newName in o)) {
warn() warn();
o[newName] = (o as any)[oldName] o[newName] = (o as any)[oldName];
} }
// wrap the deprecated property in an accessor to warn // wrap the deprecated property in an accessor to warn
// and redirect to the new property // and redirect to the new property
return Object.defineProperty(o, oldName, { return Object.defineProperty(o, oldName, {
get: () => { get: () => {
warn() warn();
return o[newName] return o[newName];
}, },
set: value => { set: value => {
warn() warn();
o[newName] = value o[newName] = value;
}
})
} }
});
} }
};
export default deprecate export default deprecate;

View File

@@ -5,4 +5,4 @@ export const commonModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'shell', loader: () => require('./shell') }, { name: 'shell', loader: () => require('./shell') },
// The internal modules, invisible unless you know their names. // The internal modules, invisible unless you know their names.
{ name: 'deprecate', loader: () => require('./deprecate'), private: true } { name: 'deprecate', loader: () => require('./deprecate'), private: true }
] ];

View File

@@ -1,8 +1,5 @@
'use strict' 'use strict';
const { deprecate } = require('electron') const { nativeImage } = process.electronBinding('native_image');
const { NativeImage, nativeImage } = process.electronBinding('native_image')
deprecate.fnToProperty(NativeImage.prototype, 'isMacTemplateImage', '_isTemplateImage', '_setTemplateImage') module.exports = nativeImage;
module.exports = nativeImage

View File

@@ -1,3 +1,3 @@
'use strict' 'use strict';
module.exports = process.electronBinding('shell') module.exports = process.electronBinding('shell');

Some files were not shown because too many files have changed in this diff Show More