mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
15 Commits
v7.0.0-nig
...
v7.0.0-nig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a19e55a902 | ||
|
|
9187759460 | ||
|
|
04dd52e4dc | ||
|
|
90caedb552 | ||
|
|
87ae9324ac | ||
|
|
26155c8a00 | ||
|
|
81366b5bfb | ||
|
|
c436997840 | ||
|
|
b180fb376c | ||
|
|
ab70e854f8 | ||
|
|
a31faaae61 | ||
|
|
1e3e5a6619 | ||
|
|
ac35f41e8d | ||
|
|
554ee92b39 | ||
|
|
02dc1b266c |
2
DEPS
2
DEPS
@@ -12,7 +12,7 @@ vars = {
|
||||
'chromium_version':
|
||||
'ab588d36191964c4bca8de5c320534d95606c861',
|
||||
'node_version':
|
||||
'a86a4a160dc520c61a602c949a32a1bc4c0fc633',
|
||||
'dee0db9864a001ffc16440f725f4952a1a417069',
|
||||
'nan_version':
|
||||
'960dd6c70fc9eb136efdf37b4bef18fadbc3436f',
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
7.0.0-nightly.20190530
|
||||
7.0.0-nightly.20190602
|
||||
@@ -268,4 +268,8 @@ void AtomContentClient::AddContentDecryptionModules(
|
||||
}
|
||||
}
|
||||
|
||||
bool AtomContentClient::IsDataResourceGzipped(int resource_id) const {
|
||||
return ui::ResourceBundle::GetSharedInstance().IsGzipped(resource_id);
|
||||
}
|
||||
|
||||
} // namespace atom
|
||||
|
||||
@@ -31,6 +31,7 @@ class AtomContentClient : public content::ContentClient {
|
||||
void AddContentDecryptionModules(
|
||||
std::vector<content::CdmInfo>* cdms,
|
||||
std::vector<media::CdmHostFilePath>* cdm_host_file_paths) override;
|
||||
bool IsDataResourceGzipped(int resource_id) const override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(AtomContentClient);
|
||||
|
||||
@@ -149,10 +149,12 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) {
|
||||
settings.lock_log = logging::LOCK_LOG_FILE;
|
||||
settings.delete_old = logging::DELETE_OLD_LOG_FILE;
|
||||
#else
|
||||
settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
|
||||
settings.logging_dest =
|
||||
logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
|
||||
#endif // defined(DEBUG)
|
||||
#else // defined(OS_WIN)
|
||||
settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
|
||||
settings.logging_dest =
|
||||
logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
|
||||
#endif // !defined(OS_WIN)
|
||||
|
||||
// Only enable logging when --enable-logging is specified.
|
||||
|
||||
@@ -1286,6 +1286,13 @@ std::string App::GetUserAgentFallback() {
|
||||
return AtomBrowserClient::Get()->GetUserAgent();
|
||||
}
|
||||
|
||||
void App::SetBrowserClientCanUseCustomSiteInstance(bool should_disable) {
|
||||
AtomBrowserClient::Get()->SetCanUseCustomSiteInstance(should_disable);
|
||||
}
|
||||
bool App::CanBrowserClientUseCustomSiteInstance() {
|
||||
return AtomBrowserClient::Get()->CanUseCustomSiteInstance();
|
||||
}
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
bool App::MoveToApplicationsFolder(mate::Arguments* args) {
|
||||
return ui::cocoa::AtomBundleMover::Move(args);
|
||||
@@ -1467,7 +1474,10 @@ void App::BuildPrototype(v8::Isolate* isolate,
|
||||
#endif
|
||||
.SetProperty("userAgentFallback", &App::GetUserAgentFallback,
|
||||
&App::SetUserAgentFallback)
|
||||
.SetMethod("enableSandbox", &App::EnableSandbox);
|
||||
.SetMethod("enableSandbox", &App::EnableSandbox)
|
||||
.SetProperty("allowRendererProcessReuse",
|
||||
&App::CanBrowserClientUseCustomSiteInstance,
|
||||
&App::SetBrowserClientCanUseCustomSiteInstance);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -213,6 +213,8 @@ class App : public AtomBrowserClient::Delegate,
|
||||
void EnableSandbox(mate::Arguments* args);
|
||||
void SetUserAgentFallback(const std::string& user_agent);
|
||||
std::string GetUserAgentFallback();
|
||||
void SetBrowserClientCanUseCustomSiteInstance(bool should_disable);
|
||||
bool CanBrowserClientUseCustomSiteInstance();
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
bool MoveToApplicationsFolder(mate::Arguments* args);
|
||||
|
||||
@@ -106,6 +106,9 @@ void SystemPreferences::BuildPrototype(
|
||||
&SystemPreferences::GetAppLevelAppearance)
|
||||
.SetMethod("setAppLevelAppearance",
|
||||
&SystemPreferences::SetAppLevelAppearance)
|
||||
.SetProperty("appLevelAppearance",
|
||||
&SystemPreferences::GetAppLevelAppearance,
|
||||
&SystemPreferences::SetAppLevelAppearance)
|
||||
.SetMethod("getSystemColor", &SystemPreferences::GetSystemColor)
|
||||
.SetMethod("canPromptTouchID", &SystemPreferences::CanPromptTouchID)
|
||||
.SetMethod("promptTouchID", &SystemPreferences::PromptTouchID)
|
||||
|
||||
@@ -924,6 +924,14 @@ void WebContents::Message(bool internal,
|
||||
internal, channel, std::move(arguments));
|
||||
}
|
||||
|
||||
void WebContents::Invoke(const std::string& channel,
|
||||
base::Value arguments,
|
||||
InvokeCallback callback) {
|
||||
// webContents.emit('-ipc-invoke', new Event(), channel, arguments);
|
||||
EmitWithSender("-ipc-invoke", bindings_.dispatch_context(),
|
||||
std::move(callback), channel, std::move(arguments));
|
||||
}
|
||||
|
||||
void WebContents::MessageSync(bool internal,
|
||||
const std::string& channel,
|
||||
base::Value arguments,
|
||||
|
||||
@@ -492,6 +492,9 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
||||
void Message(bool internal,
|
||||
const std::string& channel,
|
||||
base::Value arguments) override;
|
||||
void Invoke(const std::string& channel,
|
||||
base::Value arguments,
|
||||
InvokeCallback callback) override;
|
||||
void MessageSync(bool internal,
|
||||
const std::string& channel,
|
||||
base::Value arguments,
|
||||
|
||||
@@ -58,7 +58,7 @@ void Event::PreventDefault(v8::Isolate* isolate) {
|
||||
.Check();
|
||||
}
|
||||
|
||||
bool Event::SendReply(const base::ListValue& result) {
|
||||
bool Event::SendReply(const base::Value& result) {
|
||||
if (!callback_ || sender_ == nullptr)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -32,8 +32,9 @@ class Event : public Wrappable<Event>, public content::WebContentsObserver {
|
||||
// event.PreventDefault().
|
||||
void PreventDefault(v8::Isolate* isolate);
|
||||
|
||||
// event.sendReply(array), used for replying synchronous message.
|
||||
bool SendReply(const base::ListValue& result);
|
||||
// event.sendReply(value), used for replying to synchronous messages and
|
||||
// `invoke` calls.
|
||||
bool SendReply(const base::Value& result);
|
||||
|
||||
protected:
|
||||
explicit Event(v8::Isolate* isolate);
|
||||
|
||||
@@ -403,6 +403,14 @@ void AtomBrowserClient::OverrideWebkitPrefs(content::RenderViewHost* host,
|
||||
web_preferences->OverrideWebkitPrefs(prefs);
|
||||
}
|
||||
|
||||
void AtomBrowserClient::SetCanUseCustomSiteInstance(bool should_disable) {
|
||||
disable_process_restart_tricks_ = should_disable;
|
||||
}
|
||||
|
||||
bool AtomBrowserClient::CanUseCustomSiteInstance() {
|
||||
return disable_process_restart_tricks_;
|
||||
}
|
||||
|
||||
content::ContentBrowserClient::SiteInstanceForNavigationType
|
||||
AtomBrowserClient::ShouldOverrideSiteInstanceForNavigation(
|
||||
content::RenderFrameHost* current_rfh,
|
||||
@@ -509,6 +517,10 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
||||
web_preferences->AppendCommandLineSwitches(command_line);
|
||||
SessionPreferences::AppendExtraCommandLineSwitches(
|
||||
web_contents->GetBrowserContext(), command_line);
|
||||
if (CanUseCustomSiteInstance()) {
|
||||
command_line->AppendSwitch(
|
||||
switches::kDisableElectronSiteInstanceOverrides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,9 @@ class AtomBrowserClient : public content::ContentBrowserClient,
|
||||
std::string GetUserAgent() const override;
|
||||
void SetUserAgent(const std::string& user_agent);
|
||||
|
||||
void SetCanUseCustomSiteInstance(bool should_disable);
|
||||
bool CanUseCustomSiteInstance() override;
|
||||
|
||||
protected:
|
||||
void RenderProcessWillLaunch(
|
||||
content::RenderProcessHost* host,
|
||||
@@ -253,6 +256,8 @@ class AtomBrowserClient : public content::ContentBrowserClient,
|
||||
|
||||
std::string user_agent_override_ = "";
|
||||
|
||||
bool disable_process_restart_tricks_ = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AtomBrowserClient);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>electron.icns</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>7.0.0-nightly.20190530</string>
|
||||
<string>7.0.0-nightly.20190602</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>7.0.0-nightly.20190530</string>
|
||||
<string>7.0.0-nightly.20190602</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.developer-tools</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 7,0,0,20190530
|
||||
PRODUCTVERSION 7,0,0,20190530
|
||||
FILEVERSION 7,0,0,20190602
|
||||
PRODUCTVERSION 7,0,0,20190602
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
||||
@@ -21,6 +21,12 @@ interface ElectronBrowser {
|
||||
string channel,
|
||||
mojo_base.mojom.ListValue arguments);
|
||||
|
||||
// Emits an event on |channel| from the ipcMain JavaScript object in the main
|
||||
// process, and returns the response.
|
||||
Invoke(
|
||||
string channel,
|
||||
mojo_base.mojom.ListValue arguments) => (mojo_base.mojom.Value result);
|
||||
|
||||
// Emits an event on |channel| from the ipcMain JavaScript object in the main
|
||||
// process, and waits synchronously for a response.
|
||||
//
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#define ATOM_MINOR_VERSION 0
|
||||
#define ATOM_PATCH_VERSION 0
|
||||
// clang-format off
|
||||
#define ATOM_PRE_RELEASE_VERSION -nightly.20190530
|
||||
#define ATOM_PRE_RELEASE_VERSION -nightly.20190602
|
||||
// clang-format on
|
||||
|
||||
#ifndef ATOM_STRINGIFY
|
||||
|
||||
@@ -232,6 +232,8 @@ const char kScrollBounce[] = "scroll-bounce";
|
||||
const char kHiddenPage[] = "hidden-page";
|
||||
const char kNativeWindowOpen[] = "native-window-open";
|
||||
const char kWebviewTag[] = "webview-tag";
|
||||
const char kDisableElectronSiteInstanceOverrides[] =
|
||||
"disable-electron-site-instance-overrides";
|
||||
|
||||
// Command switch passed to renderer process to control nodeIntegration.
|
||||
const char kNodeIntegrationInWorker[] = "node-integration-in-worker";
|
||||
|
||||
@@ -118,6 +118,7 @@ extern const char kNodeIntegrationInWorker[];
|
||||
extern const char kWebviewTag[];
|
||||
extern const char kNodeIntegrationInSubFrames[];
|
||||
extern const char kDisableHtmlFullscreenWindowResize[];
|
||||
extern const char kDisableElectronSiteInstanceOverrides[];
|
||||
|
||||
extern const char kWidevineCdmPath[];
|
||||
extern const char kWidevineCdmVersion[];
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "atom/common/native_mate_converters/value_converter.h"
|
||||
#include "atom/common/node_bindings.h"
|
||||
#include "atom/common/node_includes.h"
|
||||
#include "atom/common/promise_util.h"
|
||||
#include "base/task/post_task.h"
|
||||
#include "base/values.h"
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
@@ -41,6 +42,8 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
|
||||
DCHECK(render_frame);
|
||||
render_frame->GetRemoteInterfaces()->GetInterface(
|
||||
mojo::MakeRequest(&electron_browser_ptr_));
|
||||
render_frame->GetRemoteInterfaces()->GetInterface(
|
||||
mojo::MakeRequest(&electron_browser_sync_ptr_));
|
||||
}
|
||||
static void BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype) {
|
||||
@@ -49,7 +52,8 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
|
||||
.SetMethod("send", &IPCRenderer::Send)
|
||||
.SetMethod("sendSync", &IPCRenderer::SendSync)
|
||||
.SetMethod("sendTo", &IPCRenderer::SendTo)
|
||||
.SetMethod("sendToHost", &IPCRenderer::SendToHost);
|
||||
.SetMethod("sendToHost", &IPCRenderer::SendToHost)
|
||||
.SetMethod("invoke", &IPCRenderer::Invoke);
|
||||
}
|
||||
static mate::Handle<IPCRenderer> Create(v8::Isolate* isolate) {
|
||||
return mate::CreateHandle(isolate, new IPCRenderer(isolate));
|
||||
@@ -62,6 +66,20 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
|
||||
electron_browser_ptr_->Message(internal, channel, arguments.Clone());
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> Invoke(mate::Arguments* args,
|
||||
const std::string& channel,
|
||||
const base::Value& arguments) {
|
||||
atom::util::Promise p(args->isolate());
|
||||
auto handle = p.GetHandle();
|
||||
electron_browser_ptr_->Invoke(
|
||||
channel, arguments.Clone(),
|
||||
base::BindOnce(
|
||||
[](atom::util::Promise p, base::Value value) { p.Resolve(value); },
|
||||
std::move(p)));
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void SendTo(mate::Arguments* args,
|
||||
bool internal,
|
||||
bool send_to_all,
|
||||
@@ -82,6 +100,52 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
const base::ListValue& arguments) {
|
||||
// We aren't using a true synchronous mojo call here. We're calling an
|
||||
// asynchronous method and blocking on the result. The reason we're doing
|
||||
// this is a little complicated, so buckle up.
|
||||
//
|
||||
// Mojo has a concept of synchronous calls. However, synchronous calls are
|
||||
// dangerous. In particular, it's quite possible for two processes to call
|
||||
// synchronous methods on each other and cause a deadlock. Mojo has a
|
||||
// mechanism to avoid this kind of deadlock: if a process is waiting on the
|
||||
// result of a synchronous call, and it receives an incoming call for a
|
||||
// synchronous method, it will process that request immediately, even
|
||||
// though it's currently blocking. However, if it receives an incoming
|
||||
// request for an _asynchronous_ method, that can't cause a deadlock, so it
|
||||
// stashes the request on a queue to be processed once the synchronous
|
||||
// thing it's waiting on returns.
|
||||
//
|
||||
// This behavior is useful for preventing deadlocks, but it is inconvenient
|
||||
// here because it can result in messages being reordered. If the main
|
||||
// process is awaiting the result of a synchronous call (which it does only
|
||||
// very rarely, since it's bad to block the main process), and we send
|
||||
// first an asynchronous message to the main process, followed by a
|
||||
// synchronous message, then the main process will process the synchronous
|
||||
// one first.
|
||||
//
|
||||
// It turns out, Electron has some dependency on message ordering,
|
||||
// especially during window shutdown, and getting messages out of order can
|
||||
// result in, for example, remote objects disappearing unexpectedly. To
|
||||
// avoid these issues and guarantee consistent message ordering, we send
|
||||
// all messages to the main process as asynchronous messages. This causes
|
||||
// them to always be queued and processed in the same order they were
|
||||
// received, even if they were received while the main process was waiting
|
||||
// on a synchronous call.
|
||||
//
|
||||
// However, in the calling process, we still need to block on the result,
|
||||
// because the caller is expecting a result synchronously. So we do a bit
|
||||
// of a trick: we pass the Mojo handle over to a new thread, send the
|
||||
// asynchronous message from that thread, and then block on the result.
|
||||
// It's important that we pass the handle over to the new thread, because
|
||||
// that allows Mojo to process incoming messages (most importantly, the
|
||||
// response to our request) on the new thread. If we didn't pass it to a
|
||||
// new thread, and instead sent the call from the main thread, we would
|
||||
// never receive a response because Mojo wouldn't be able to run its
|
||||
// message handling code, because the main thread would be tied up blocking
|
||||
// on the WaitableEvent.
|
||||
//
|
||||
// Phew. If you got this far, here's a gold star: ⭐️
|
||||
|
||||
base::Value result;
|
||||
|
||||
// A task is posted to a separate thread to execute the request so that
|
||||
@@ -96,7 +160,7 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
|
||||
// We unbind the interface from this thread to pass it over to the worker
|
||||
// thread temporarily. This requires that no callbacks be pending for this
|
||||
// interface.
|
||||
auto interface_info = electron_browser_ptr_.PassInterface();
|
||||
auto interface_info = electron_browser_sync_ptr_.PassInterface();
|
||||
task_runner->PostTask(
|
||||
FROM_HERE, base::BindOnce(&IPCRenderer::SendMessageSyncOnWorkerThread,
|
||||
base::Unretained(&interface_info),
|
||||
@@ -104,7 +168,7 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
|
||||
base::Unretained(&result), internal, channel,
|
||||
base::Unretained(&arguments)));
|
||||
response_received_event.Wait();
|
||||
electron_browser_ptr_.Bind(std::move(interface_info));
|
||||
electron_browser_sync_ptr_.Bind(std::move(interface_info));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -135,6 +199,10 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
|
||||
}
|
||||
|
||||
atom::mojom::ElectronBrowserPtr electron_browser_ptr_;
|
||||
|
||||
// We execute all synchronous calls on a separate mojo pipe, because
|
||||
// of the way that we block on the result of synchronous calls.
|
||||
atom::mojom::ElectronBrowserPtr electron_browser_sync_ptr_;
|
||||
};
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "atom/renderer/api/atom_api_spell_check_client.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -39,14 +41,16 @@ bool HasWordCharacters(const base::string16& text, int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct Word {
|
||||
blink::WebTextCheckingResult result;
|
||||
base::string16 text;
|
||||
std::vector<base::string16> contraction_words;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class SpellCheckClient::SpellcheckRequest {
|
||||
public:
|
||||
// Map of individual words to list of occurrences in text
|
||||
using WordMap =
|
||||
std::map<base::string16, std::vector<blink::WebTextCheckingResult>>;
|
||||
|
||||
SpellcheckRequest(
|
||||
const base::string16& text,
|
||||
std::unique_ptr<blink::WebTextCheckingCompletion> completion)
|
||||
@@ -55,11 +59,11 @@ class SpellCheckClient::SpellcheckRequest {
|
||||
|
||||
const base::string16& text() const { return text_; }
|
||||
blink::WebTextCheckingCompletion* completion() { return completion_.get(); }
|
||||
WordMap& wordmap() { return word_map_; }
|
||||
std::vector<Word>& wordlist() { return word_list_; }
|
||||
|
||||
private:
|
||||
base::string16 text_; // Text to be checked in this task.
|
||||
WordMap word_map_; // WordMap to hold distinct words in text
|
||||
base::string16 text_; // Text to be checked in this task.
|
||||
std::vector<Word> word_list_; // List of Words found in text
|
||||
// The interface to send the misspelled ranges to WebKit.
|
||||
std::unique_ptr<blink::WebTextCheckingCompletion> completion_;
|
||||
|
||||
@@ -150,9 +154,9 @@ void SpellCheckClient::SpellCheckText() {
|
||||
base::string16 word;
|
||||
size_t word_start;
|
||||
size_t word_length;
|
||||
std::vector<base::string16> words;
|
||||
auto& word_map = pending_request_param_->wordmap();
|
||||
blink::WebTextCheckingResult result;
|
||||
std::set<base::string16> words;
|
||||
auto& word_list = pending_request_param_->wordlist();
|
||||
Word word_entry;
|
||||
for (;;) { // Run until end of text
|
||||
const auto status =
|
||||
text_iterator_.GetNextWord(&word, &word_start, &word_length);
|
||||
@@ -161,23 +165,18 @@ void SpellCheckClient::SpellCheckText() {
|
||||
if (status == SpellcheckWordIterator::IS_SKIPPABLE)
|
||||
continue;
|
||||
|
||||
result.location = base::checked_cast<int>(word_start);
|
||||
result.length = base::checked_cast<int>(word_length);
|
||||
word_entry.result.location = base::checked_cast<int>(word_start);
|
||||
word_entry.result.length = base::checked_cast<int>(word_length);
|
||||
word_entry.text = word;
|
||||
word_entry.contraction_words.clear();
|
||||
|
||||
word_list.push_back(word_entry);
|
||||
words.insert(word);
|
||||
// If the given word is a concatenated word of two or more valid words
|
||||
// (e.g. "hello:hello"), we should treat it as a valid word.
|
||||
std::vector<base::string16> contraction_words;
|
||||
if (!IsContraction(scope, word, &contraction_words)) {
|
||||
words.push_back(word);
|
||||
word_map[word].push_back(result);
|
||||
} else {
|
||||
// For a contraction, we want check the spellings of each individual
|
||||
// part, but mark the entire word incorrect if any part is misspelled
|
||||
// Hence, we use the same word_start and word_length values for every
|
||||
// part of the contraction.
|
||||
for (const auto& w : contraction_words) {
|
||||
words.push_back(w);
|
||||
word_map[w].push_back(result);
|
||||
if (IsContraction(scope, word, &word_entry.contraction_words)) {
|
||||
for (const auto& w : word_entry.contraction_words) {
|
||||
words.insert(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,29 +188,35 @@ void SpellCheckClient::SpellCheckText() {
|
||||
void SpellCheckClient::OnSpellCheckDone(
|
||||
const std::vector<base::string16>& misspelled_words) {
|
||||
std::vector<blink::WebTextCheckingResult> results;
|
||||
std::unordered_set<base::string16> misspelled(misspelled_words.begin(),
|
||||
misspelled_words.end());
|
||||
|
||||
auto& word_map = pending_request_param_->wordmap();
|
||||
auto& word_list = pending_request_param_->wordlist();
|
||||
|
||||
// Take each word from the list of misspelled words received, find their
|
||||
// corresponding WebTextCheckingResult that's stored in the map and pass
|
||||
// all the results to blink through the completion callback.
|
||||
for (const auto& word : misspelled_words) {
|
||||
auto iter = word_map.find(word);
|
||||
if (iter != word_map.end()) {
|
||||
// Word found in map, now gather all the occurrences of the word
|
||||
// from the map value
|
||||
auto& words = iter->second;
|
||||
results.insert(results.end(), words.begin(), words.end());
|
||||
words.clear();
|
||||
for (const auto& word : word_list) {
|
||||
if (misspelled.find(word.text) != misspelled.end()) {
|
||||
// If this is a contraction, iterate through parts and accept the word
|
||||
// if none of them are misspelled
|
||||
if (!word.contraction_words.empty()) {
|
||||
auto all_correct = true;
|
||||
for (const auto& contraction_word : word.contraction_words) {
|
||||
if (misspelled.find(contraction_word) != misspelled.end()) {
|
||||
all_correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_correct)
|
||||
continue;
|
||||
}
|
||||
results.push_back(word.result);
|
||||
}
|
||||
}
|
||||
pending_request_param_->completion()->DidFinishCheckingText(results);
|
||||
pending_request_param_ = nullptr;
|
||||
}
|
||||
|
||||
void SpellCheckClient::SpellCheckWords(
|
||||
const SpellCheckScope& scope,
|
||||
const std::vector<base::string16>& words) {
|
||||
void SpellCheckClient::SpellCheckWords(const SpellCheckScope& scope,
|
||||
const std::set<base::string16>& words) {
|
||||
DCHECK(!scope.spell_check_.IsEmpty());
|
||||
|
||||
v8::Local<v8::FunctionTemplate> templ = mate::CreateFunctionTemplate(
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define ATOM_RENDERER_API_ATOM_API_SPELL_CHECK_CLIENT_H_
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -68,7 +69,7 @@ class SpellCheckClient : public blink::WebSpellCheckPanelHostClient,
|
||||
// The javascript function will callback OnSpellCheckDone
|
||||
// with the results of all the misspelled words.
|
||||
void SpellCheckWords(const SpellCheckScope& scope,
|
||||
const std::vector<base::string16>& words);
|
||||
const std::set<base::string16>& words);
|
||||
|
||||
// Returns whether or not the given word is a contraction of valid words
|
||||
// (e.g. "word:word").
|
||||
|
||||
@@ -104,6 +104,13 @@ void AtomRendererClient::DidCreateScriptContext(
|
||||
|
||||
// Setup node environment for each window.
|
||||
node::Environment* env = node_bindings_->CreateEnvironment(context);
|
||||
auto* command_line = base::CommandLine::ForCurrentProcess();
|
||||
// If we have disabled the site instance overrides we should prevent loading
|
||||
// any non-context aware native module
|
||||
if (command_line->HasSwitch(switches::kDisableElectronSiteInstanceOverrides))
|
||||
env->ForceOnlyContextAwareNativeModules();
|
||||
env->WarnNonContextAwareNativeModules();
|
||||
|
||||
environments_.insert(env);
|
||||
|
||||
// Add Electron extended APIs.
|
||||
@@ -145,9 +152,11 @@ void AtomRendererClient::WillReleaseScriptContext(
|
||||
// Destroy the node environment. We only do this if node support has been
|
||||
// enabled for sub-frames to avoid a change-of-behavior / introduce crashes
|
||||
// for existing users.
|
||||
// TODO(MarshallOfSOund): Free the environment regardless of this switch
|
||||
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||
switches::kNodeIntegrationInSubFrames))
|
||||
// We also do this if we have disable electron site instance overrides to
|
||||
// avoid memory leaks
|
||||
auto* command_line = base::CommandLine::ForCurrentProcess();
|
||||
if (command_line->HasSwitch(switches::kNodeIntegrationInSubFrames) ||
|
||||
command_line->HasSwitch(switches::kDisableElectronSiteInstanceOverrides))
|
||||
node::FreeEnvironment(env);
|
||||
|
||||
// ElectronBindings is tracking node environments.
|
||||
|
||||
@@ -667,7 +667,7 @@ 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
|
||||
preferred over `name` by Electron.
|
||||
|
||||
**[Deprecated Soon](modernization/property-updates.md)**
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `app.setName(name)`
|
||||
|
||||
@@ -675,7 +675,7 @@ preferred over `name` by Electron.
|
||||
|
||||
Overrides the current application's name.
|
||||
|
||||
**[Deprecated Soon](modernization/property-updates.md)**
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `app.getLocale()`
|
||||
|
||||
@@ -796,7 +796,7 @@ Returns `Object`:
|
||||
|
||||
### `app.setJumpList(categories)` _Windows_
|
||||
|
||||
* `categories` [JumpListCategory[]](structures/jump-list-category.md) or `null` - Array of `JumpListCategory` objects.
|
||||
* `categories` [JumpListCategory[]](structures/jump-list-category.md) | `null` - Array of `JumpListCategory` objects.
|
||||
|
||||
Sets or removes a custom Jump List for the application, and returns one of the
|
||||
following strings:
|
||||
@@ -1070,13 +1070,13 @@ 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,
|
||||
for more information please read [Desktop Environment Integration][unity-requirement].
|
||||
|
||||
**[Deprecated Soon](modernization/property-updates.md)**
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `app.getBadgeCount()` _Linux_ _macOS_
|
||||
|
||||
Returns `Integer` - The current value displayed in the counter badge.
|
||||
|
||||
**[Deprecated Soon](modernization/property-updates.md)**
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `app.isUnityRunning()` _Linux_
|
||||
|
||||
@@ -1152,7 +1152,7 @@ technologies, such as screen readers, has been detected. See
|
||||
https://www.chromium.org/developers/design-documents/accessibility for more
|
||||
details.
|
||||
|
||||
**[Deprecated Soon](modernization/property-updates.md)**
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `app.setAccessibilitySupportEnabled(enabled)` _macOS_ _Windows_
|
||||
|
||||
@@ -1165,7 +1165,7 @@ 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.
|
||||
|
||||
**[Deprecated Soon](modernization/property-updates.md)**
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `app.showAboutPanel()` _macOS_ _Linux_
|
||||
|
||||
@@ -1306,3 +1306,16 @@ This is the user agent that will be used when no user agent is set at the
|
||||
`webContents` or `session` level. It is useful for ensuring that your entire
|
||||
app has the same user agent. Set to a custom value as early as possible
|
||||
in your app's initialization to ensure that your overridden value is used.
|
||||
|
||||
### `app.allowRendererProcessReuse`
|
||||
|
||||
A `Boolean` which when `true` disables the overrides that Electron has in place
|
||||
to ensure renderer processes are restarted on every navigation. The current
|
||||
default value for this property is `false`.
|
||||
|
||||
The intention is for these overrides to become disabled by default and then at
|
||||
some point in the future this property will be removed. This property impacts
|
||||
which native modules you can use in the renderer process. For more information
|
||||
on the direction Electron is going with renderer process restarts and usage of
|
||||
native modules in the renderer process please check out this
|
||||
[Tracking Issue](https://github.com/electron/electron/issues/18397).
|
||||
|
||||
@@ -88,7 +88,61 @@ Removes the specified `listener` from the listener array for the specified
|
||||
|
||||
Removes listeners of the specified `channel`.
|
||||
|
||||
## Event object
|
||||
### `ipcMain.handle(channel, listener)`
|
||||
|
||||
* `channel` String
|
||||
* `listener` Function<Promise> | Function<any>
|
||||
* `event` IpcMainInvokeEvent
|
||||
* `...args` any[]
|
||||
|
||||
Adds a handler for an `invoke`able IPC. This handler will be called whenever a
|
||||
renderer calls `ipcRenderer.invoke(channel, ...args)`.
|
||||
|
||||
If `listener` returns a Promise, the eventual result of the promise will be
|
||||
returned as a reply to the remote caller. Otherwise, the return value of the
|
||||
listener will be used as the value of the reply.
|
||||
|
||||
```js
|
||||
// Main process
|
||||
ipcMain.handle('my-invokable-ipc', async (event, ...args) => {
|
||||
const result = await somePromise(...args)
|
||||
return result
|
||||
})
|
||||
|
||||
// Renderer process
|
||||
async () => {
|
||||
const result = await ipcRenderer.invoke('my-invokable-ipc', arg1, arg2)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The `event` that is passed as the first argument to the handler is the same as
|
||||
that passed to a regular event listener. It includes information about which
|
||||
WebContents is the source of the invoke request.
|
||||
|
||||
### `ipcMain.handleOnce(channel, listener)`
|
||||
|
||||
* `channel` String
|
||||
* `listener` Function<Promise> | Function<any>
|
||||
* `event` IpcMainInvokeEvent
|
||||
* `...args` any[]
|
||||
|
||||
Handles a single `invoke`able IPC message, then removes the listener. See
|
||||
`ipcMain.handle(channel, listener)`.
|
||||
|
||||
### `ipcMain.removeHandler(channel)`
|
||||
|
||||
* `channel` String
|
||||
|
||||
Removes any handler for `channel`, if present.
|
||||
|
||||
## IpcMainEvent object
|
||||
|
||||
The documentation for the `event` object passed to the `callback` can be found
|
||||
in the [`ipc-main-event`](structures/ipc-main-event.md) structure docs.
|
||||
|
||||
## IpcMainInvokeEvent object
|
||||
|
||||
The documentation for the `event` object passed to `handle` callbacks can be
|
||||
found in the [`ipc-main-invoke-event`](structures/ipc-main-invoke-event.md)
|
||||
structure docs.
|
||||
|
||||
@@ -57,10 +57,39 @@ Removes all listeners, or those of the specified `channel`.
|
||||
* `...args` any[]
|
||||
|
||||
Send a message to the main process asynchronously via `channel`, you can also
|
||||
send arbitrary arguments. Arguments will be serialized in JSON internally and
|
||||
send arbitrary arguments. Arguments will be serialized as JSON internally and
|
||||
hence no functions or prototype chain will be included.
|
||||
|
||||
The main process handles it by listening for `channel` with [`ipcMain`](ipc-main.md) module.
|
||||
The main process handles it by listening for `channel` with the
|
||||
[`ipcMain`](ipc-main.md) module.
|
||||
|
||||
### `ipcRenderer.invoke(channel[, arg1][, arg2][, ...])`
|
||||
|
||||
* `channel` String
|
||||
* `...args` any[]
|
||||
|
||||
Returns `Promise<any>` - Resolves with the response from the main process.
|
||||
|
||||
Send a message to the main process asynchronously via `channel` and expect an
|
||||
asynchronous result. Arguments will be serialized as JSON internally and
|
||||
hence no functions or prototype chain will be included.
|
||||
|
||||
The main process should listen for `channel` with
|
||||
[`ipcMain.handle()`](ipc-main.md#ipcmainhandlechannel-listener).
|
||||
|
||||
For example:
|
||||
```javascript
|
||||
// Renderer process
|
||||
ipcRenderer.invoke('some-name', someArgument).then((result) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
// Main process
|
||||
ipcMain.handle('some-name', async (event, someArgument) => {
|
||||
const result = await doSomeWork(someArgument)
|
||||
return result
|
||||
})
|
||||
```
|
||||
|
||||
### `ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])`
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ The following additional roles are available on _macOS_:
|
||||
* `moveTabToNewWindow` - Map to the `moveTabToNewWindow` action.
|
||||
* `window` - The submenu is a "Window" menu.
|
||||
* `help` - The submenu is a "Help" menu.
|
||||
* `services` - The submenu is a "Services" menu.
|
||||
* `services` - The submenu is a ["Services"](https://developer.apple.com/documentation/appkit/nsapplication/1428608-servicesmenu?language=objc) menu. This is only intended for use in the Application Menu and is *not* the same as the "Services" submenu used in context menus in macOS apps, which is not implemented in Electron.
|
||||
* `recentDocuments` - The submenu is an "Open Recent" menu.
|
||||
* `clearRecentDocuments` - Map to the `clearRecentDocuments` action.
|
||||
|
||||
|
||||
@@ -277,7 +277,6 @@ window.addEventListener('contextmenu', (e) => {
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
## Notes on macOS Application Menu
|
||||
|
||||
macOS has a completely different style of application menu from Windows and
|
||||
@@ -285,7 +284,7 @@ Linux. Here are some notes on making your app's menu more native-like.
|
||||
|
||||
### Standard Menus
|
||||
|
||||
On macOS there are many system-defined standard menus, like the `Services` and
|
||||
On macOS there are many system-defined standard menus, like the [`Services`](https://developer.apple.com/documentation/appkit/nsapplication/1428608-servicesmenu?language=objc) and
|
||||
`Windows` menus. To make your menu a standard menu, you should set your menu's
|
||||
`role` to one of the following and Electron will recognize them and make them
|
||||
become standard menus:
|
||||
|
||||
@@ -32,8 +32,6 @@ The Electron team is currently undergoing an initiative to convert separate gett
|
||||
* `paused`
|
||||
* `Session` module
|
||||
* `preloads`
|
||||
* `SystemPreferences` module
|
||||
* `appLevelAppearance`
|
||||
* `webContents` module
|
||||
* `zoomFactor`
|
||||
* `zoomLevel`
|
||||
@@ -58,3 +56,5 @@ The Electron team is currently undergoing an initiative to convert separate gett
|
||||
* `name`
|
||||
* `NativeImage`
|
||||
* `isMacTemplateImage`
|
||||
* `SystemPreferences` module
|
||||
* `appLevelAppearance`
|
||||
|
||||
@@ -275,13 +275,13 @@ Returns [`Size`](structures/size.md)
|
||||
|
||||
Marks the image as a template image.
|
||||
|
||||
**[Deprecated Soon](modernization/property-updates.md)**
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
#### `image.isTemplateImage()`
|
||||
|
||||
Returns `Boolean` - Whether the image is a template image.
|
||||
|
||||
**[Deprecated Soon](modernization/property-updates.md)**
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
#### `image.crop(rect)`
|
||||
|
||||
|
||||
@@ -5,4 +5,3 @@
|
||||
* `sender` WebContents - Returns the `webContents` that sent the message
|
||||
* `reply` Function - A function that will send an IPC message to the renderer frame that sent the original message that you are currently handling. You should use this method to "reply" to the sent message in order to guaruntee the reply will go to the correct process and frame.
|
||||
* `...args` any[]
|
||||
IpcRenderer
|
||||
|
||||
4
docs/api/structures/ipc-main-invoke-event.md
Normal file
4
docs/api/structures/ipc-main-invoke-event.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# IpcMainInvokeEvent Object extends `Event`
|
||||
|
||||
* `frameId` Integer - The ID of the renderer frame that sent this message
|
||||
* `sender` WebContents - Returns the `webContents` that sent the message
|
||||
@@ -380,12 +380,16 @@ You can use the `setAppLevelAppearance` API to set this value.
|
||||
Sets the appearance setting for your application, this should override the
|
||||
system default and override the value of `getEffectiveAppearance`.
|
||||
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `systemPreferences.canPromptTouchID()` _macOS_
|
||||
|
||||
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.
|
||||
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `systemPreferences.promptTouchID(reason)` _macOS_
|
||||
|
||||
* `reason` String - The reason you are asking for Touch ID authentication
|
||||
@@ -439,3 +443,13 @@ Returns `Object`:
|
||||
* `prefersReducedMotion` Boolean - Determines whether the user desires reduced motion based on platform APIs.
|
||||
|
||||
Returns an object with system animation settings.
|
||||
|
||||
## Properties
|
||||
|
||||
### `systemPreferences.appLevelAppearance` _macOS_
|
||||
|
||||
A `String` property that determines the macOS appearance setting for
|
||||
your application. This maps to values in: [NSApplication.appearance](https://developer.apple.com/documentation/appkit/nsapplication/2967170-appearance?language=objc). Setting this will override the
|
||||
system default as well as the value of `getEffectiveAppearance`.
|
||||
|
||||
Possible values that can be set are `dark` and `light`, and possible return values are `dark`, `light`, and `unknown`.
|
||||
|
||||
@@ -78,6 +78,7 @@ auto_filenames = {
|
||||
"docs/api/structures/gpu-feature-status.md",
|
||||
"docs/api/structures/io-counters.md",
|
||||
"docs/api/structures/ipc-main-event.md",
|
||||
"docs/api/structures/ipc-main-invoke-event.md",
|
||||
"docs/api/structures/ipc-renderer-event.md",
|
||||
"docs/api/structures/jump-list-category.md",
|
||||
"docs/api/structures/jump-list-item.md",
|
||||
|
||||
@@ -1,8 +1,40 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { IpcMainInvokeEvent } from 'electron'
|
||||
|
||||
const emitter = new EventEmitter()
|
||||
class IpcMain extends EventEmitter {
|
||||
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();
|
||||
|
||||
handle: Electron.IpcMain['handle'] = (method, fn) => {
|
||||
if (this._invokeHandlers.has(method)) {
|
||||
throw new Error(`Attempted to register a second handler for '${method}'`)
|
||||
}
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`)
|
||||
}
|
||||
this._invokeHandlers.set(method, async (e, ...args) => {
|
||||
try {
|
||||
(e as any)._reply(await Promise.resolve(fn(e, ...args)))
|
||||
} catch (err) {
|
||||
(e as any)._throw(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => {
|
||||
this.handle(method, (e, ...args) => {
|
||||
this.removeHandler(method)
|
||||
return fn(e, ...args)
|
||||
})
|
||||
}
|
||||
|
||||
removeHandler (method: string) {
|
||||
this._invokeHandlers.delete(method)
|
||||
}
|
||||
}
|
||||
|
||||
const ipcMain = new IpcMain()
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
emitter.on('error', () => {})
|
||||
ipcMain.on('error', () => {})
|
||||
|
||||
export default emitter
|
||||
export default ipcMain
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
'use strict'
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { deprecate } = require('electron')
|
||||
const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences')
|
||||
|
||||
// SystemPreferences is an EventEmitter.
|
||||
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype)
|
||||
EventEmitter.call(systemPreferences)
|
||||
|
||||
if ('appLevelAppearance' in systemPreferences) {
|
||||
deprecate.fnToProperty(systemPreferences, 'appLevelAppearance', '_getAppLevelAppearance', '_setAppLevelAppearance')
|
||||
}
|
||||
|
||||
module.exports = systemPreferences
|
||||
|
||||
@@ -326,6 +326,19 @@ WebContents.prototype._init = function () {
|
||||
}
|
||||
})
|
||||
|
||||
this.on('-ipc-invoke', function (event, channel, args) {
|
||||
event._reply = (result) => event.sendReply({ result })
|
||||
event._throw = (error) => {
|
||||
console.error(`Error occurred in handler for '${channel}':`, error)
|
||||
event.sendReply({ error: error.toString() })
|
||||
}
|
||||
if (ipcMain._invokeHandlers.has(channel)) {
|
||||
ipcMain._invokeHandlers.get(channel)(event, ...args)
|
||||
} else {
|
||||
event._throw(`No handler registered for '${channel}'`)
|
||||
}
|
||||
})
|
||||
|
||||
this.on('-ipc-message-sync', function (event, internal, channel, args) {
|
||||
addReturnValueToEvent(event)
|
||||
if (internal) {
|
||||
|
||||
@@ -27,4 +27,11 @@ ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
|
||||
return ipc.sendTo(internal, true, webContentsId, channel, args)
|
||||
}
|
||||
|
||||
ipcRenderer.invoke = function (channel, ...args) {
|
||||
return ipc.invoke(channel, args).then(({ error, result }) => {
|
||||
if (error) { throw new Error(`Error invoking remote method '${channel}': ${error}`) }
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = ipcRenderer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "7.0.0-nightly.20190530",
|
||||
"version": "7.0.0-nightly.20190602",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -9,7 +9,6 @@ browser_compositor_mac.patch
|
||||
can_create_window.patch
|
||||
disable_hidden.patch
|
||||
dom_storage_limits.patch
|
||||
frame_host_manager.patch
|
||||
out_of_process_instance.patch
|
||||
render_widget_host_view_base.patch
|
||||
render_widget_host_view_mac.patch
|
||||
@@ -58,11 +57,9 @@ command-ismediakey.patch
|
||||
tts.patch
|
||||
printing.patch
|
||||
verbose_generate_breakpad_symbols.patch
|
||||
cross_site_document_resource_handler.patch
|
||||
content_allow_embedder_to_prevent_locking_scheme_registry.patch
|
||||
support_mixed_sandbox_with_zygote.patch
|
||||
disable_color_correct_rendering.patch
|
||||
disable_time_ticks_dcheck.patch
|
||||
autofill_size_calculation.patch
|
||||
revert_build_swiftshader_for_arm32.patch
|
||||
fix_disable_usage_of_abort_report_np_in_mas_builds.patch
|
||||
@@ -79,3 +76,5 @@ disable_custom_libcxx_on_windows.patch
|
||||
feat_offscreen_rendering_with_viz_compositor.patch
|
||||
worker_context_will_destroy.patch
|
||||
fix_breakpad_symbol_generation_on_linux_arm.patch
|
||||
cross_site_document_resource_handler.patch
|
||||
frame_host_manager.patch
|
||||
|
||||
@@ -22,31 +22,31 @@ index 2151c5b9698e9a2768875d04a2297956cc4c0d41..8a316a117ab367bcbac947894cbe1bc2
|
||||
}
|
||||
|
||||
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
|
||||
index 2317ddf6a3c533f701fe44bf1b8114eb042c2189..9a823a98175c33c84b8d44a95c9d7d44568807ca 100644
|
||||
index 3729dcc9ea3272c943754a92c6ed1d7a1fd8fcf3..787cd81b26508d3e04956721f0e7cec2f457aa60 100644
|
||||
--- a/content/public/browser/content_browser_client.cc
|
||||
+++ b/content/public/browser/content_browser_client.cc
|
||||
@@ -61,6 +61,10 @@ ContentBrowserClient::SiteInstanceForNavigationType ContentBrowserClient::Should
|
||||
return SiteInstanceForNavigationType::ASK_CHROMIUM;
|
||||
@@ -56,6 +56,10 @@ BrowserMainParts* ContentBrowserClient::CreateBrowserMainParts(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
+bool ContentBrowserClient::ShouldBypassCORB(int render_process_id) const {
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
BrowserMainParts* ContentBrowserClient::CreateBrowserMainParts(
|
||||
const MainFunctionParams& parameters) {
|
||||
return nullptr;
|
||||
void ContentBrowserClient::PostAfterStartupTask(
|
||||
const base::Location& from_here,
|
||||
const scoped_refptr<base::TaskRunner>& task_runner,
|
||||
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
|
||||
index a81f40507b2233c3bde03b940cccd6be0aaa4926..1a208d24bae80277e4a60f4180bb7f95c38561ce 100644
|
||||
index 4c84fb3648b3de36015b325246559f8aefe2ebd5..bf9b3a601fc16d5070e4467a258a047f47b059f3 100644
|
||||
--- a/content/public/browser/content_browser_client.h
|
||||
+++ b/content/public/browser/content_browser_client.h
|
||||
@@ -242,6 +242,9 @@ class CONTENT_EXPORT ContentBrowserClient {
|
||||
content::RenderFrameHost* rfh,
|
||||
content::SiteInstance* pending_site_instance) {}
|
||||
@@ -219,6 +219,9 @@ class CONTENT_EXPORT ContentBrowserClient {
|
||||
virtual BrowserMainParts* CreateBrowserMainParts(
|
||||
const MainFunctionParams& parameters);
|
||||
|
||||
+ // Electron: Allows bypassing CORB checks for a renderer process.
|
||||
+ virtual bool ShouldBypassCORB(int render_process_id) const;
|
||||
+
|
||||
// Allows the embedder to set any number of custom BrowserMainParts
|
||||
// implementations for the browser startup code. See comments in
|
||||
// browser_main_parts.h.
|
||||
// Allows the embedder to change the default behavior of
|
||||
// BrowserThread::PostAfterStartupTask to better match whatever
|
||||
// definition of "startup" the embedder has in mind. This may be
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Cheng Zhao <zcbenz@gmail.com>
|
||||
Date: Tue, 15 Jan 2019 14:57:02 -0700
|
||||
Subject: disable_time_ticks_dcheck.patch
|
||||
|
||||
The DCHECK is failing for some reason.
|
||||
|
||||
diff --git a/content/common/inter_process_time_ticks_converter.cc b/content/common/inter_process_time_ticks_converter.cc
|
||||
index 128abab37eb8a96535ef92ebf11a463e863cc485..4d8e5e9c05b11083a69537d5badc85924b6fbae2 100644
|
||||
--- a/content/common/inter_process_time_ticks_converter.cc
|
||||
+++ b/content/common/inter_process_time_ticks_converter.cc
|
||||
@@ -55,13 +55,13 @@ LocalTimeTicks InterProcessTimeTicksConverter::ToLocalTimeTicks(
|
||||
|
||||
RemoteTimeDelta remote_delta = remote_time_ticks - remote_lower_bound_;
|
||||
|
||||
- DCHECK_LE(remote_time_ticks, remote_upper_bound_);
|
||||
+ // DCHECK_LE(remote_time_ticks, remote_upper_bound_);
|
||||
return local_base_time_ + ToLocalTimeDelta(remote_delta);
|
||||
}
|
||||
|
||||
LocalTimeDelta InterProcessTimeTicksConverter::ToLocalTimeDelta(
|
||||
RemoteTimeDelta remote_delta) const {
|
||||
- DCHECK_LE(remote_lower_bound_ + remote_delta, remote_upper_bound_);
|
||||
+ // DCHECK_LE(remote_lower_bound_ + remote_delta, remote_upper_bound_);
|
||||
|
||||
// For remote times that come before remote time range, apply just time
|
||||
// offset and ignore scaling, so as to avoid extrapolation error for values
|
||||
@@ -4,94 +4,104 @@ Date: Wed, 14 Nov 2018 20:38:46 +0530
|
||||
Subject: frame_host_manager.patch
|
||||
|
||||
Allows embedder to intercept site instances chosen by chromium
|
||||
and respond with custom instance.
|
||||
and respond with custom instance. Also allows for us to at-runtime
|
||||
enable or disable this patch.
|
||||
|
||||
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
|
||||
index b5301d164138f21ca8ae01abfb153efde51ec324..91efc5f94f61f7bccc2ff802851097df8eb52818 100644
|
||||
index b5301d164138f21ca8ae01abfb153efde51ec324..886b6d3beb3e2d7b13a15af830bea6fb5a567cba 100644
|
||||
--- a/content/browser/frame_host/render_frame_host_manager.cc
|
||||
+++ b/content/browser/frame_host/render_frame_host_manager.cc
|
||||
@@ -2127,6 +2127,16 @@ bool RenderFrameHostManager::InitRenderView(
|
||||
@@ -2127,6 +2127,20 @@ bool RenderFrameHostManager::InitRenderView(
|
||||
scoped_refptr<SiteInstance>
|
||||
RenderFrameHostManager::GetSiteInstanceForNavigationRequest(
|
||||
const NavigationRequest& request) {
|
||||
+ BrowserContext* browser_context =
|
||||
+ delegate_->GetControllerForRenderManager().GetBrowserContext();
|
||||
+ // If the navigation can swap SiteInstances, compute the SiteInstance it
|
||||
+ // should use.
|
||||
+ // TODO(clamy): We should also consider as a candidate SiteInstance the
|
||||
+ // speculative SiteInstance that was computed on redirects.
|
||||
+ scoped_refptr<SiteInstance> candidate_site_instance =
|
||||
+ speculative_render_frame_host_
|
||||
+ ? speculative_render_frame_host_->GetSiteInstance()
|
||||
+ : nullptr;
|
||||
+ BrowserContext* browser_context = nullptr;
|
||||
+ scoped_refptr<SiteInstance> candidate_site_instance;
|
||||
+ if (!GetContentClient()->browser()->CanUseCustomSiteInstance()) {
|
||||
+ browser_context =
|
||||
+ delegate_->GetControllerForRenderManager().GetBrowserContext();
|
||||
+ // If the navigation can swap SiteInstances, compute the SiteInstance it
|
||||
+ // should use.
|
||||
+ // TODO(clamy): We should also consider as a candidate SiteInstance the
|
||||
+ // speculative SiteInstance that was computed on redirects.
|
||||
+ candidate_site_instance =
|
||||
+ speculative_render_frame_host_
|
||||
+ ? speculative_render_frame_host_->GetSiteInstance()
|
||||
+ : nullptr;
|
||||
+ }
|
||||
// First, check if the navigation can switch SiteInstances. If not, the
|
||||
// navigation should use the current SiteInstance.
|
||||
SiteInstance* current_site_instance = render_frame_host_->GetSiteInstance();
|
||||
@@ -2159,6 +2169,51 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest(
|
||||
@@ -2159,6 +2173,53 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest(
|
||||
request.common_params().url);
|
||||
no_renderer_swap_allowed |=
|
||||
request.from_begin_navigation() && !can_renderer_initiate_transfer;
|
||||
+
|
||||
+ bool has_response_started =
|
||||
+ (request.state() == NavigationRequest::RESPONSE_STARTED ||
|
||||
+ request.state() == NavigationRequest::FAILED) &&
|
||||
+ !speculative_render_frame_host_;
|
||||
+ // Gives user a chance to choose a custom site instance.
|
||||
+ SiteInstance* affinity_site_instance = nullptr;
|
||||
+ scoped_refptr<SiteInstance> overriden_site_instance;
|
||||
+ ContentBrowserClient::SiteInstanceForNavigationType siteInstanceType =
|
||||
+ GetContentClient()->browser()->ShouldOverrideSiteInstanceForNavigation(
|
||||
+ current_frame_host(), speculative_frame_host(), browser_context,
|
||||
+ request.common_params().url, has_response_started,
|
||||
+ &affinity_site_instance);
|
||||
+ switch (siteInstanceType) {
|
||||
+ case ContentBrowserClient::SiteInstanceForNavigationType::
|
||||
+ FORCE_CANDIDATE_OR_NEW:
|
||||
+ overriden_site_instance =
|
||||
+ candidate_site_instance
|
||||
+ ? candidate_site_instance
|
||||
+ : SiteInstance::CreateForURL(browser_context,
|
||||
+ request.common_params().url);
|
||||
+ break;
|
||||
+ case ContentBrowserClient::SiteInstanceForNavigationType::FORCE_CURRENT:
|
||||
+ overriden_site_instance = render_frame_host_->GetSiteInstance();
|
||||
+ break;
|
||||
+ case ContentBrowserClient::SiteInstanceForNavigationType::FORCE_AFFINITY:
|
||||
+ DCHECK(affinity_site_instance);
|
||||
+ overriden_site_instance =
|
||||
+ scoped_refptr<SiteInstance>(affinity_site_instance);
|
||||
+ break;
|
||||
+ case ContentBrowserClient::SiteInstanceForNavigationType::ASK_CHROMIUM:
|
||||
+ DCHECK(!affinity_site_instance);
|
||||
+ break;
|
||||
+ default:
|
||||
+ break;
|
||||
+ }
|
||||
+ if (overriden_site_instance) {
|
||||
+ if (siteInstanceType ==
|
||||
+ ContentBrowserClient::SiteInstanceForNavigationType::
|
||||
+ FORCE_CANDIDATE_OR_NEW) {
|
||||
+ GetContentClient()->browser()->RegisterPendingSiteInstance(
|
||||
+ render_frame_host_.get(), overriden_site_instance.get());
|
||||
+ if (!GetContentClient()->browser()->CanUseCustomSiteInstance()) {
|
||||
+ bool has_response_started =
|
||||
+ (request.state() == NavigationRequest::RESPONSE_STARTED ||
|
||||
+ request.state() == NavigationRequest::FAILED) &&
|
||||
+ !speculative_render_frame_host_;
|
||||
+ // Gives user a chance to choose a custom site instance.
|
||||
+ SiteInstance* affinity_site_instance = nullptr;
|
||||
+ scoped_refptr<SiteInstance> overriden_site_instance;
|
||||
+ ContentBrowserClient::SiteInstanceForNavigationType siteInstanceType =
|
||||
+ GetContentClient()->browser()->ShouldOverrideSiteInstanceForNavigation(
|
||||
+ current_frame_host(), speculative_frame_host(), browser_context,
|
||||
+ request.common_params().url, has_response_started,
|
||||
+ &affinity_site_instance);
|
||||
+ switch (siteInstanceType) {
|
||||
+ case ContentBrowserClient::SiteInstanceForNavigationType::
|
||||
+ FORCE_CANDIDATE_OR_NEW:
|
||||
+ overriden_site_instance =
|
||||
+ candidate_site_instance
|
||||
+ ? candidate_site_instance
|
||||
+ : SiteInstance::CreateForURL(browser_context,
|
||||
+ request.common_params().url);
|
||||
+ break;
|
||||
+ case ContentBrowserClient::SiteInstanceForNavigationType::FORCE_CURRENT:
|
||||
+ overriden_site_instance = render_frame_host_->GetSiteInstance();
|
||||
+ break;
|
||||
+ case ContentBrowserClient::SiteInstanceForNavigationType::FORCE_AFFINITY:
|
||||
+ DCHECK(affinity_site_instance);
|
||||
+ overriden_site_instance =
|
||||
+ scoped_refptr<SiteInstance>(affinity_site_instance);
|
||||
+ break;
|
||||
+ case ContentBrowserClient::SiteInstanceForNavigationType::ASK_CHROMIUM:
|
||||
+ DCHECK(!affinity_site_instance);
|
||||
+ break;
|
||||
+ default:
|
||||
+ break;
|
||||
+ }
|
||||
+ if (overriden_site_instance) {
|
||||
+ if (siteInstanceType ==
|
||||
+ ContentBrowserClient::SiteInstanceForNavigationType::
|
||||
+ FORCE_CANDIDATE_OR_NEW) {
|
||||
+ GetContentClient()->browser()->RegisterPendingSiteInstance(
|
||||
+ render_frame_host_.get(), overriden_site_instance.get());
|
||||
+ }
|
||||
+ return overriden_site_instance;
|
||||
+ }
|
||||
+ return overriden_site_instance;
|
||||
+ }
|
||||
} else {
|
||||
// Subframe navigations will use the current renderer, unless specifically
|
||||
// allowed to swap processes.
|
||||
@@ -2170,23 +2225,17 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest(
|
||||
@@ -2170,23 +2231,28 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest(
|
||||
if (no_renderer_swap_allowed && !should_swap_for_error_isolation)
|
||||
return scoped_refptr<SiteInstance>(current_site_instance);
|
||||
|
||||
- // If the navigation can swap SiteInstances, compute the SiteInstance it
|
||||
- // should use.
|
||||
- // TODO(clamy): We should also consider as a candidate SiteInstance the
|
||||
- // speculative SiteInstance that was computed on redirects.
|
||||
+ if (GetContentClient()->browser()->CanUseCustomSiteInstance()) {
|
||||
// If the navigation can swap SiteInstances, compute the SiteInstance it
|
||||
// should use.
|
||||
// TODO(clamy): We should also consider as a candidate SiteInstance the
|
||||
// speculative SiteInstance that was computed on redirects.
|
||||
- SiteInstance* candidate_site_instance =
|
||||
- speculative_render_frame_host_
|
||||
- ? speculative_render_frame_host_->GetSiteInstance()
|
||||
- : nullptr;
|
||||
-
|
||||
+ candidate_site_instance =
|
||||
speculative_render_frame_host_
|
||||
? speculative_render_frame_host_->GetSiteInstance()
|
||||
: nullptr;
|
||||
+ }
|
||||
|
||||
scoped_refptr<SiteInstance> dest_site_instance = GetSiteInstanceForNavigation(
|
||||
request.common_params().url, request.source_site_instance(),
|
||||
- request.dest_site_instance(), candidate_site_instance,
|
||||
@@ -108,13 +118,17 @@ index b5301d164138f21ca8ae01abfb153efde51ec324..91efc5f94f61f7bccc2ff802851097df
|
||||
}
|
||||
|
||||
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
|
||||
index 3729dcc9ea3272c943754a92c6ed1d7a1fd8fcf3..2317ddf6a3c533f701fe44bf1b8114eb042c2189 100644
|
||||
index 787cd81b26508d3e04956721f0e7cec2f457aa60..8e62a5dd26595411757e03078ed0e44646c47a52 100644
|
||||
--- a/content/public/browser/content_browser_client.cc
|
||||
+++ b/content/public/browser/content_browser_client.cc
|
||||
@@ -51,6 +51,16 @@ void OverrideOnBindInterface(const service_manager::BindSourceInfo& remote_info,
|
||||
@@ -51,6 +51,20 @@ void OverrideOnBindInterface(const service_manager::BindSourceInfo& remote_info,
|
||||
handle);
|
||||
}
|
||||
|
||||
+bool ContentBrowserClient::CanUseCustomSiteInstance() {
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
+ContentBrowserClient::SiteInstanceForNavigationType ContentBrowserClient::ShouldOverrideSiteInstanceForNavigation(
|
||||
+ content::RenderFrameHost* current_rfh,
|
||||
+ content::RenderFrameHost* speculative_rfh,
|
||||
@@ -129,10 +143,10 @@ index 3729dcc9ea3272c943754a92c6ed1d7a1fd8fcf3..2317ddf6a3c533f701fe44bf1b8114eb
|
||||
const MainFunctionParams& parameters) {
|
||||
return nullptr;
|
||||
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
|
||||
index 4c84fb3648b3de36015b325246559f8aefe2ebd5..a81f40507b2233c3bde03b940cccd6be0aaa4926 100644
|
||||
index bf9b3a601fc16d5070e4467a258a047f47b059f3..3c35eddc2498157c2b98bab55991d8aa195954f6 100644
|
||||
--- a/content/public/browser/content_browser_client.h
|
||||
+++ b/content/public/browser/content_browser_client.h
|
||||
@@ -211,8 +211,37 @@ CONTENT_EXPORT void OverrideOnBindInterface(
|
||||
@@ -211,8 +211,40 @@ CONTENT_EXPORT void OverrideOnBindInterface(
|
||||
// the observer interfaces.)
|
||||
class CONTENT_EXPORT ContentBrowserClient {
|
||||
public:
|
||||
@@ -153,6 +167,9 @@ index 4c84fb3648b3de36015b325246559f8aefe2ebd5..a81f40507b2233c3bde03b940cccd6be
|
||||
+ };
|
||||
virtual ~ContentBrowserClient() {}
|
||||
|
||||
+ // Electron: Allows disabling the below ShouldOverride patch
|
||||
+ virtual bool CanUseCustomSiteInstance();
|
||||
+
|
||||
+ // Electron: Allows overriding the SiteInstance when navigating.
|
||||
+ virtual SiteInstanceForNavigationType ShouldOverrideSiteInstanceForNavigation(
|
||||
+ content::RenderFrameHost* current_rfh,
|
||||
|
||||
@@ -3,7 +3,5 @@
|
||||
|
||||
"src/electron/patches/common/boringssl": "src/third_party/boringssl/src",
|
||||
|
||||
"src/electron/patches/common/ffmpeg": "src/third_party/ffmpeg",
|
||||
|
||||
"src/electron/patches/common/v8": "src/v8"
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
build_gn.patch
|
||||
@@ -1,24 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Ales Pergl <alpergl@microsoft.com>
|
||||
Date: Mon, 22 Oct 2018 10:45:25 -0700
|
||||
Subject: build_gn.patch
|
||||
|
||||
Chromium's Mac toolchain sets the "install_name" linker parameter only
|
||||
when "is_component_build" is true, but we want to set even if it's false,
|
||||
because we are making a dylib which will be distributed inside a bundle.
|
||||
|
||||
diff --git a/BUILD.gn b/BUILD.gn
|
||||
index fd79bc4eaba90ab5e8c6df9942dbfaeeebabbc73..1e1859d5aa1567da8a768c870d8a477b052f50ec 100755
|
||||
--- a/BUILD.gn
|
||||
+++ b/BUILD.gn
|
||||
@@ -397,6 +397,10 @@ if (is_component_ffmpeg) {
|
||||
# So we can append below and assume they're defined.
|
||||
ldflags = []
|
||||
|
||||
+ if (!is_component_build && is_mac) {
|
||||
+ ldflags += [ "-Wl,-install_name,@rpath/libffmpeg.dylib" ]
|
||||
+ }
|
||||
+
|
||||
if (is_fuchsia || (is_posix && !is_mac)) {
|
||||
# Fixes warnings PIC relocation when building as component.
|
||||
ldflags += [
|
||||
@@ -49,11 +49,30 @@ async function downloadArtifact (name, buildNum, dest) {
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.log(`Downloading ${artifactToDownload.url}.`)
|
||||
await downloadFile(artifactToDownload.url, dest)
|
||||
console.log(`Successfully downloaded ${name}.`)
|
||||
let downloadError = false
|
||||
await downloadWithRetry(artifactToDownload.url, dest).catch(err => {
|
||||
console.log(`Error downnloading ${artifactToDownload.url} :`, err)
|
||||
downloadError = true
|
||||
})
|
||||
if (!downloadError) {
|
||||
console.log(`Successfully downloaded ${name}.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadWithRetry (url, directory) {
|
||||
let lastError
|
||||
for (let i = 0; i < 5; i++) {
|
||||
console.log(`Attempting to download ${url} - attempt #${(i + 1)}`)
|
||||
try {
|
||||
return await downloadFile(url, directory)
|
||||
} catch (err) {
|
||||
lastError = err
|
||||
}
|
||||
}
|
||||
throw lastError
|
||||
}
|
||||
|
||||
function downloadFile (url, directory) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const nuggetOpts = {
|
||||
|
||||
@@ -1332,6 +1332,26 @@ describe('default behavior', () => {
|
||||
expect(app.userAgentFallback).to.equal(initialValue)
|
||||
})
|
||||
})
|
||||
|
||||
describe('app.allowRendererProcessReuse', () => {
|
||||
it('should default to false', () => {
|
||||
expect(app.allowRendererProcessReuse).to.equal(false)
|
||||
})
|
||||
|
||||
it('should cause renderer processes to get new PIDs when false', async () => {
|
||||
const output = await runTestApp('site-instance-overrides', 'false')
|
||||
expect(output[0]).to.be.a('number').that.is.greaterThan(0)
|
||||
expect(output[1]).to.be.a('number').that.is.greaterThan(0)
|
||||
expect(output[0]).to.not.equal(output[1])
|
||||
})
|
||||
|
||||
it('should cause renderer processes to keep the same PID when true', async () => {
|
||||
const output = await runTestApp('site-instance-overrides', 'true')
|
||||
expect(output[0]).to.be.a('number').that.is.greaterThan(0)
|
||||
expect(output[1]).to.be.a('number').that.is.greaterThan(0)
|
||||
expect(output[0]).to.equal(output[1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
async function runTestApp (name: string, ...args: any[]) {
|
||||
|
||||
109
spec-main/api-ipc-spec.ts
Normal file
109
spec-main/api-ipc-spec.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import * as chai from 'chai'
|
||||
import * as chaiAsPromised from 'chai-as-promised'
|
||||
import { BrowserWindow, ipcMain, IpcMainInvokeEvent } from 'electron'
|
||||
|
||||
const { expect } = chai
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
|
||||
describe('ipc module', () => {
|
||||
describe('invoke', () => {
|
||||
let w = (null as unknown as BrowserWindow);
|
||||
|
||||
before(async () => {
|
||||
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
await w.loadURL('about:blank')
|
||||
})
|
||||
after(async () => {
|
||||
w.destroy()
|
||||
})
|
||||
|
||||
async function rendererInvoke(...args: any[]) {
|
||||
const {ipcRenderer} = require('electron')
|
||||
try {
|
||||
const result = await ipcRenderer.invoke('test', ...args)
|
||||
ipcRenderer.send('result', {result})
|
||||
} catch (e) {
|
||||
ipcRenderer.send('result', {error: e.message})
|
||||
}
|
||||
}
|
||||
|
||||
it('receives a response from a synchronous handler', async () => {
|
||||
ipcMain.handleOnce('test', (e: IpcMainInvokeEvent, arg: number) => {
|
||||
expect(arg).to.equal(123)
|
||||
return 3
|
||||
})
|
||||
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
|
||||
expect(arg).to.deep.equal({result: 3})
|
||||
resolve()
|
||||
}))
|
||||
await w.webContents.executeJavaScript(`(${rendererInvoke})(123)`)
|
||||
await done
|
||||
})
|
||||
|
||||
it('receives a response from an asynchronous handler', async () => {
|
||||
ipcMain.handleOnce('test', async (e: IpcMainInvokeEvent, arg: number) => {
|
||||
expect(arg).to.equal(123)
|
||||
await new Promise(resolve => setImmediate(resolve))
|
||||
return 3
|
||||
})
|
||||
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
|
||||
expect(arg).to.deep.equal({result: 3})
|
||||
resolve()
|
||||
}))
|
||||
await w.webContents.executeJavaScript(`(${rendererInvoke})(123)`)
|
||||
await done
|
||||
})
|
||||
|
||||
it('receives an error from a synchronous handler', async () => {
|
||||
ipcMain.handleOnce('test', () => {
|
||||
throw new Error('some error')
|
||||
})
|
||||
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
|
||||
expect(arg.error).to.match(/some error/)
|
||||
resolve()
|
||||
}))
|
||||
await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
|
||||
await done
|
||||
})
|
||||
|
||||
it('receives an error from an asynchronous handler', async () => {
|
||||
ipcMain.handleOnce('test', async () => {
|
||||
await new Promise(resolve => setImmediate(resolve))
|
||||
throw new Error('some error')
|
||||
})
|
||||
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
|
||||
expect(arg.error).to.match(/some error/)
|
||||
resolve()
|
||||
}))
|
||||
await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
|
||||
await done
|
||||
})
|
||||
|
||||
it('throws an error if no handler is registered', async () => {
|
||||
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
|
||||
expect(arg.error).to.match(/No handler registered/)
|
||||
resolve()
|
||||
}))
|
||||
await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
|
||||
await done
|
||||
})
|
||||
|
||||
it('throws an error when invoking a handler that was removed', async () => {
|
||||
ipcMain.handle('test', () => {})
|
||||
ipcMain.removeHandler('test')
|
||||
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
|
||||
expect(arg.error).to.match(/No handler registered/)
|
||||
resolve()
|
||||
}))
|
||||
await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
|
||||
await done
|
||||
})
|
||||
|
||||
it('forbids multiple handlers', async () => {
|
||||
ipcMain.handle('test', () => {})
|
||||
expect(() => { ipcMain.handle('test', () => {}) }).to.throw(/second handler/)
|
||||
ipcMain.removeHandler('test')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -9,7 +9,7 @@ const ChildProcess = require('child_process')
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
const { emittedOnce } = require('./events-helpers')
|
||||
|
||||
const { session, BrowserWindow, net } = require('electron')
|
||||
const { session, BrowserWindow, net, ipcMain } = require('electron')
|
||||
const { expect } = chai
|
||||
|
||||
/* The whole session API doesn't use standard callbacks */
|
||||
@@ -26,7 +26,8 @@ describe('session module', () => {
|
||||
width: 400,
|
||||
height: 400,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
nodeIntegration: true,
|
||||
webviewTag: true,
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -62,23 +63,18 @@ describe('session module', () => {
|
||||
const name = '0'
|
||||
const value = '0'
|
||||
|
||||
it('should get cookies', (done) => {
|
||||
it('should get cookies', async () => {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Set-Cookie', [`${name}=${value}`])
|
||||
res.end('finished')
|
||||
server.close()
|
||||
})
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
w.webContents.once('did-finish-load', async () => {
|
||||
const list = await w.webContents.session.cookies.get({ url })
|
||||
const cookie = list.find(cookie => cookie.name === name)
|
||||
|
||||
expect(cookie).to.exist.and.to.have.property('value', value)
|
||||
done()
|
||||
})
|
||||
const { port } = server.address()
|
||||
w.loadURL(`${url}:${port}`)
|
||||
})
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
||||
const { port } = server.address()
|
||||
await w.loadURL(`${url}:${port}`)
|
||||
const list = await w.webContents.session.cookies.get({ url })
|
||||
const cookie = list.find(cookie => cookie.name === name)
|
||||
expect(cookie).to.exist.and.to.have.property('value', value)
|
||||
})
|
||||
|
||||
it('sets cookies', async () => {
|
||||
@@ -92,17 +88,12 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
it('yields an error when setting a cookie with missing required fields', async () => {
|
||||
let error
|
||||
try {
|
||||
await expect((async () => {
|
||||
const { cookies } = session.defaultSession
|
||||
const name = '1'
|
||||
const value = '1'
|
||||
await cookies.set({ url: '', name, value })
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
expect(error).is.an('Error')
|
||||
expect(error).to.have.property('message').which.equals('Failed to get cookie domain')
|
||||
})()).to.eventually.be.rejectedWith('Failed to get cookie domain')
|
||||
})
|
||||
|
||||
it('should overwrite previous cookies', async () => {
|
||||
@@ -173,7 +164,7 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
describe('ses.cookies.flushStore()', async () => {
|
||||
describe('flushes the cookies to disk', async () => {
|
||||
it('flushes the cookies to disk', async () => {
|
||||
const name = 'foo'
|
||||
const value = 'bar'
|
||||
const { cookies } = session.defaultSession
|
||||
@@ -185,29 +176,26 @@ describe('session module', () => {
|
||||
|
||||
it('should survive an app restart for persistent partition', async () => {
|
||||
const appPath = path.join(fixtures, 'api', 'cookie-app')
|
||||
const electronPath = process.execPath
|
||||
|
||||
const test = (result, phase) => {
|
||||
const runAppWithPhase = (phase) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let output = ''
|
||||
|
||||
const appProcess = ChildProcess.spawn(
|
||||
electronPath,
|
||||
process.execPath,
|
||||
[appPath],
|
||||
{ env: { PHASE: phase, ...process.env } }
|
||||
)
|
||||
|
||||
appProcess.stdout.on('data', data => { output += data })
|
||||
appProcess.stdout.on('end', () => {
|
||||
output = output.replace(/(\r\n|\n|\r)/gm, '')
|
||||
expect(output).to.equal(result)
|
||||
resolve()
|
||||
resolve(output.replace(/(\r\n|\n|\r)/gm, ''))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
await test('011', 'one')
|
||||
await test('110', 'two')
|
||||
expect(await runAppWithPhase('one')).to.equal('011')
|
||||
expect(await runAppWithPhase('two')).to.equal('110')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -228,16 +216,7 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
describe('will-download event', () => {
|
||||
beforeEach(() => {
|
||||
if (w != null) w.destroy()
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
width: 400,
|
||||
height: 400
|
||||
})
|
||||
})
|
||||
|
||||
it('can cancel default download behavior', (done) => {
|
||||
it('can cancel default download behavior', async () => {
|
||||
const mockFile = Buffer.alloc(1024)
|
||||
const contentDisposition = 'inline; filename="mockFile.txt"'
|
||||
const downloadServer = http.createServer((req, res) => {
|
||||
@@ -249,22 +228,23 @@ describe('session module', () => {
|
||||
res.end(mockFile)
|
||||
downloadServer.close()
|
||||
})
|
||||
await new Promise(resolve => downloadServer.listen(0, '127.0.0.1', resolve))
|
||||
|
||||
downloadServer.listen(0, '127.0.0.1', () => {
|
||||
const port = downloadServer.address().port
|
||||
const url = `http://127.0.0.1:${port}/`
|
||||
const port = downloadServer.address().port
|
||||
const url = `http://127.0.0.1:${port}/`
|
||||
|
||||
const downloadPrevented = new Promise(resolve => {
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
e.preventDefault()
|
||||
expect(item.getURL()).to.equal(url)
|
||||
expect(item.getFilename()).to.equal('mockFile.txt')
|
||||
setImmediate(() => {
|
||||
expect(() => item.getURL()).to.throw('Object has been destroyed')
|
||||
done()
|
||||
})
|
||||
resolve(item)
|
||||
})
|
||||
w.loadURL(url)
|
||||
})
|
||||
w.loadURL(url)
|
||||
const item = await downloadPrevented
|
||||
expect(item.getURL()).to.equal(url)
|
||||
expect(item.getFilename()).to.equal('mockFile.txt')
|
||||
await new Promise(setImmediate)
|
||||
expect(() => item.getURL()).to.throw('Object has been destroyed')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -277,7 +257,7 @@ describe('session module', () => {
|
||||
callback({ data: 'test', mimeType: 'text/html' })
|
||||
}
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach(async () => {
|
||||
if (w != null) w.destroy()
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
@@ -286,13 +266,11 @@ describe('session module', () => {
|
||||
}
|
||||
})
|
||||
customSession = session.fromPartition(partitionName)
|
||||
customSession.protocol.registerStringProtocol(protocolName, handler, (error) => {
|
||||
done(error != null ? error : undefined)
|
||||
})
|
||||
await customSession.protocol.registerStringProtocol(protocolName, handler)
|
||||
})
|
||||
|
||||
afterEach((done) => {
|
||||
customSession.protocol.unregisterProtocol(protocolName, () => done())
|
||||
afterEach(async () => {
|
||||
await customSession.protocol.unregisterProtocol(protocolName)
|
||||
customSession = null
|
||||
})
|
||||
|
||||
@@ -314,13 +292,13 @@ describe('session module', () => {
|
||||
let server = null
|
||||
let customSession = null
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach(async () => {
|
||||
customSession = session.fromPartition('proxyconfig')
|
||||
// FIXME(deepak1556): This is just a hack to force
|
||||
// creation of request context which in turn initializes
|
||||
// the network context, can be removed with network
|
||||
// service enabled.
|
||||
customSession.clearHostResolverCache().then(() => done())
|
||||
await customSession.clearHostResolverCache()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -362,19 +340,11 @@ describe('session module', () => {
|
||||
})
|
||||
res.end(pac)
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(0, '127.0.0.1', async () => {
|
||||
try {
|
||||
const config = { pacScript: `http://127.0.0.1:${server.address().port}` }
|
||||
await customSession.setProxy(config)
|
||||
const proxy = await customSession.resolveProxy('https://google.com')
|
||||
expect(proxy).to.equal('PROXY myproxy:8132')
|
||||
resolve()
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
||||
const config = { pacScript: `http://127.0.0.1:${server.address().port}` }
|
||||
await customSession.setProxy(config)
|
||||
const proxy = await customSession.resolveProxy('https://google.com')
|
||||
expect(proxy).to.equal('PROXY myproxy:8132')
|
||||
})
|
||||
|
||||
it('allows bypassing proxy settings', async () => {
|
||||
@@ -389,24 +359,14 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
describe('ses.getBlobData()', () => {
|
||||
const scheme = 'cors-blob'
|
||||
const protocol = session.defaultSession.protocol
|
||||
const url = `${scheme}://host`
|
||||
after(async () => {
|
||||
await protocol.unregisterProtocol(scheme)
|
||||
})
|
||||
|
||||
it('returns blob data for uuid', (done) => {
|
||||
const scheme = 'cors-blob'
|
||||
const protocol = session.defaultSession.protocol
|
||||
const url = `${scheme}://host`
|
||||
before(() => {
|
||||
if (w != null) w.destroy()
|
||||
w = new BrowserWindow({ show: false })
|
||||
})
|
||||
|
||||
after((done) => {
|
||||
protocol.unregisterProtocol(scheme, () => {
|
||||
closeWindow(w).then(() => {
|
||||
w = null
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const postData = JSON.stringify({
|
||||
type: 'blob',
|
||||
value: 'hello'
|
||||
@@ -460,26 +420,23 @@ describe('session module', () => {
|
||||
server.listen(0, '127.0.0.1', done)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
afterEach((done) => {
|
||||
session.defaultSession.setCertificateVerifyProc(null)
|
||||
server.close()
|
||||
server.close(done)
|
||||
})
|
||||
|
||||
it('accepts the request when the callback is called with 0', (done) => {
|
||||
it('accepts the request when the callback is called with 0', async () => {
|
||||
session.defaultSession.setCertificateVerifyProc(({ hostname, certificate, verificationResult, errorCode }, callback) => {
|
||||
expect(['net::ERR_CERT_AUTHORITY_INVALID', 'net::ERR_CERT_COMMON_NAME_INVALID'].includes(verificationResult)).to.be.true
|
||||
expect([-202, -200].includes(errorCode)).to.be.true
|
||||
callback(0)
|
||||
})
|
||||
|
||||
w.webContents.once('did-finish-load', () => {
|
||||
expect(w.webContents.getTitle()).to.equal('hello')
|
||||
done()
|
||||
})
|
||||
w.loadURL(`https://127.0.0.1:${server.address().port}`)
|
||||
await w.loadURL(`https://127.0.0.1:${server.address().port}`)
|
||||
expect(w.webContents.getTitle()).to.equal('hello')
|
||||
})
|
||||
|
||||
it('rejects the request when the callback is called with -2', (done) => {
|
||||
it('rejects the request when the callback is called with -2', async () => {
|
||||
session.defaultSession.setCertificateVerifyProc(({ hostname, certificate, verificationResult }, callback) => {
|
||||
expect(hostname).to.equal('127.0.0.1')
|
||||
expect(certificate.issuerName).to.equal('Intermediate CA')
|
||||
@@ -496,11 +453,8 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
const url = `https://127.0.0.1:${server.address().port}`
|
||||
w.webContents.once('did-finish-load', () => {
|
||||
expect(w.webContents.getTitle()).to.equal(url)
|
||||
done()
|
||||
})
|
||||
w.loadURL(url)
|
||||
await expect(w.loadURL(url)).to.eventually.be.rejectedWith(/ERR_FAILED/)
|
||||
expect(w.webContents.getTitle()).to.equal(url)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -557,4 +511,292 @@ describe('session module', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('DownloadItem', () => {
|
||||
const mockPDF = Buffer.alloc(1024 * 1024 * 5)
|
||||
const downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf')
|
||||
const protocolName = 'custom-dl'
|
||||
const contentDisposition = 'inline; filename="mock.pdf"'
|
||||
let address = null
|
||||
let downloadServer = null
|
||||
before(async () => {
|
||||
downloadServer = http.createServer((req, res) => {
|
||||
address = downloadServer.address()
|
||||
res.writeHead(200, {
|
||||
'Content-Length': mockPDF.length,
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': req.url === '/?testFilename' ? 'inline' : contentDisposition
|
||||
})
|
||||
res.end(mockPDF)
|
||||
})
|
||||
await new Promise(resolve => downloadServer.listen(0, '127.0.0.1', resolve))
|
||||
})
|
||||
after(async () => {
|
||||
await new Promise(resolve => downloadServer.close(resolve))
|
||||
})
|
||||
|
||||
const isPathEqual = (path1, path2) => {
|
||||
return path.relative(path1, path2) === ''
|
||||
}
|
||||
const assertDownload = (state, item, isCustom = false) => {
|
||||
expect(state).to.equal('completed')
|
||||
expect(item.getFilename()).to.equal('mock.pdf')
|
||||
expect(path.isAbsolute(item.getSavePath())).to.equal(true)
|
||||
expect(isPathEqual(item.getSavePath(), downloadFilePath)).to.equal(true)
|
||||
if (isCustom) {
|
||||
expect(item.getURL()).to.equal(`${protocolName}://item`)
|
||||
} else {
|
||||
expect(item.getURL()).to.be.equal(`${url}:${address.port}/`)
|
||||
}
|
||||
expect(item.getMimeType()).to.equal('application/pdf')
|
||||
expect(item.getReceivedBytes()).to.equal(mockPDF.length)
|
||||
expect(item.getTotalBytes()).to.equal(mockPDF.length)
|
||||
expect(item.getContentDisposition()).to.equal(contentDisposition)
|
||||
expect(fs.existsSync(downloadFilePath)).to.equal(true)
|
||||
fs.unlinkSync(downloadFilePath)
|
||||
}
|
||||
|
||||
it('can download using WebContents.downloadURL', (done) => {
|
||||
const port = downloadServer.address().port
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(downloadFilePath)
|
||||
item.on('done', function (e, state) {
|
||||
assertDownload(state, item)
|
||||
done()
|
||||
})
|
||||
})
|
||||
w.webContents.downloadURL(`${url}:${port}`)
|
||||
})
|
||||
|
||||
it('can download from custom protocols using WebContents.downloadURL', (done) => {
|
||||
const protocol = session.defaultSession.protocol
|
||||
const port = downloadServer.address().port
|
||||
const handler = (ignoredError, callback) => {
|
||||
callback({ url: `${url}:${port}` })
|
||||
}
|
||||
protocol.registerHttpProtocol(protocolName, handler, (error) => {
|
||||
if (error) return done(error)
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(downloadFilePath)
|
||||
item.on('done', function (e, state) {
|
||||
assertDownload(state, item, true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
w.webContents.downloadURL(`${protocolName}://item`)
|
||||
})
|
||||
})
|
||||
|
||||
it('can download using WebView.downloadURL', async () => {
|
||||
const port = downloadServer.address().port
|
||||
await w.loadURL('about:blank')
|
||||
function webviewDownload({fixtures, url, port}) {
|
||||
const webview = new WebView()
|
||||
webview.addEventListener('did-finish-load', () => {
|
||||
webview.downloadURL(`${url}:${port}/`)
|
||||
})
|
||||
webview.src = `file://${fixtures}/api/blank.html`
|
||||
document.body.appendChild(webview)
|
||||
}
|
||||
const done = new Promise(resolve => {
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(downloadFilePath)
|
||||
item.on('done', function (e, state) {
|
||||
resolve([state, item])
|
||||
})
|
||||
})
|
||||
})
|
||||
await w.webContents.executeJavaScript(`(${webviewDownload})(${JSON.stringify({fixtures, url, port})})`)
|
||||
const [state, item] = await done
|
||||
assertDownload(state, item)
|
||||
})
|
||||
|
||||
it('can cancel download', (done) => {
|
||||
const port = downloadServer.address().port
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(downloadFilePath)
|
||||
item.on('done', function (e, state) {
|
||||
expect(state).to.equal('cancelled')
|
||||
expect(item.getFilename()).to.equal('mock.pdf')
|
||||
expect(item.getMimeType()).to.equal('application/pdf')
|
||||
expect(item.getReceivedBytes()).to.equal(0)
|
||||
expect(item.getTotalBytes()).to.equal(mockPDF.length)
|
||||
expect(item.getContentDisposition()).to.equal(contentDisposition)
|
||||
done()
|
||||
})
|
||||
item.cancel()
|
||||
})
|
||||
w.webContents.downloadURL(`${url}:${port}/`)
|
||||
})
|
||||
|
||||
it('can generate a default filename', function (done) {
|
||||
if (process.env.APPVEYOR === 'True') {
|
||||
// FIXME(alexeykuzmin): Skip the test.
|
||||
// this.skip()
|
||||
return done()
|
||||
}
|
||||
|
||||
const port = downloadServer.address().port
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(downloadFilePath)
|
||||
item.on('done', function (e, state) {
|
||||
expect(item.getFilename()).to.equal('download.pdf')
|
||||
done()
|
||||
})
|
||||
item.cancel()
|
||||
})
|
||||
w.webContents.downloadURL(`${url}:${port}/?testFilename`)
|
||||
})
|
||||
|
||||
it('can set options for the save dialog', (done) => {
|
||||
const filePath = path.join(__dirname, 'fixtures', 'mock.pdf')
|
||||
const port = downloadServer.address().port
|
||||
const options = {
|
||||
window: null,
|
||||
title: 'title',
|
||||
message: 'message',
|
||||
buttonLabel: 'buttonLabel',
|
||||
nameFieldLabel: 'nameFieldLabel',
|
||||
defaultPath: '/',
|
||||
filters: [{
|
||||
name: '1', extensions: ['.1', '.2']
|
||||
}, {
|
||||
name: '2', extensions: ['.3', '.4', '.5']
|
||||
}],
|
||||
showsTagField: true,
|
||||
securityScopedBookmarks: true
|
||||
}
|
||||
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(filePath)
|
||||
item.setSaveDialogOptions(options)
|
||||
item.on('done', function (e, state) {
|
||||
expect(item.getSaveDialogOptions()).to.deep.equal(options)
|
||||
done()
|
||||
})
|
||||
item.cancel()
|
||||
})
|
||||
w.webContents.downloadURL(`${url}:${port}`)
|
||||
})
|
||||
|
||||
describe('when a save path is specified and the URL is unavailable', () => {
|
||||
it('does not display a save dialog and reports the done state as interrupted', (done) => {
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(downloadFilePath)
|
||||
if (item.getState() === 'interrupted') {
|
||||
item.resume()
|
||||
}
|
||||
item.on('done', function (e, state) {
|
||||
expect(state).to.equal('interrupted')
|
||||
done()
|
||||
})
|
||||
})
|
||||
w.webContents.downloadURL(`file://${path.join(__dirname, 'does-not-exist.txt')}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('ses.createInterruptedDownload(options)', () => {
|
||||
it('can create an interrupted download item', (done) => {
|
||||
const downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf')
|
||||
const options = {
|
||||
path: downloadFilePath,
|
||||
urlChain: ['http://127.0.0.1/'],
|
||||
mimeType: 'application/pdf',
|
||||
offset: 0,
|
||||
length: 5242880
|
||||
}
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
expect(item.getState()).to.equal('interrupted')
|
||||
item.cancel()
|
||||
expect(item.getURLChain()).to.deep.equal(options.urlChain)
|
||||
expect(item.getMimeType()).to.equal(options.mimeType)
|
||||
expect(item.getReceivedBytes()).to.equal(options.offset)
|
||||
expect(item.getTotalBytes()).to.equal(options.length)
|
||||
expect(item.getSavePath()).to.equal(downloadFilePath)
|
||||
done()
|
||||
})
|
||||
w.webContents.session.createInterruptedDownload(options)
|
||||
})
|
||||
|
||||
it('can be resumed', async () => {
|
||||
const downloadFilePath = path.join(fixtures, 'logo.png')
|
||||
const rangeServer = http.createServer((req, res) => {
|
||||
const options = { root: fixtures }
|
||||
send(req, req.url, options)
|
||||
.on('error', (error) => { done(error) }).pipe(res)
|
||||
})
|
||||
try {
|
||||
await new Promise(resolve => rangeServer.listen(0, '127.0.0.1', resolve))
|
||||
const port = rangeServer.address().port
|
||||
const downloadCancelled = new Promise((resolve) => {
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(downloadFilePath)
|
||||
item.on('done', function (e, state) {
|
||||
resolve(item)
|
||||
})
|
||||
item.cancel()
|
||||
})
|
||||
})
|
||||
const downloadUrl = `http://127.0.0.1:${port}/assets/logo.png`
|
||||
w.webContents.downloadURL(downloadUrl)
|
||||
const item = await downloadCancelled
|
||||
expect(item.getState()).to.equal('cancelled')
|
||||
|
||||
const options = {
|
||||
path: item.getSavePath(),
|
||||
urlChain: item.getURLChain(),
|
||||
mimeType: item.getMimeType(),
|
||||
offset: item.getReceivedBytes(),
|
||||
length: item.getTotalBytes(),
|
||||
lastModified: item.getLastModifiedTime(),
|
||||
eTag: item.getETag(),
|
||||
}
|
||||
const downloadResumed = new Promise((resolve) => {
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
expect(item.getState()).to.equal('interrupted')
|
||||
item.setSavePath(downloadFilePath)
|
||||
item.resume()
|
||||
item.on('done', function (e, state) {
|
||||
resolve(item)
|
||||
})
|
||||
})
|
||||
})
|
||||
w.webContents.session.createInterruptedDownload(options)
|
||||
const completedItem = await downloadResumed
|
||||
expect(completedItem.getState()).to.equal('completed')
|
||||
expect(completedItem.getFilename()).to.equal('logo.png')
|
||||
expect(completedItem.getSavePath()).to.equal(downloadFilePath)
|
||||
expect(completedItem.getURL()).to.equal(downloadUrl)
|
||||
expect(completedItem.getMimeType()).to.equal('image/png')
|
||||
expect(completedItem.getReceivedBytes()).to.equal(14022)
|
||||
expect(completedItem.getTotalBytes()).to.equal(14022)
|
||||
expect(fs.existsSync(downloadFilePath)).to.equal(true)
|
||||
} finally {
|
||||
rangeServer.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('ses.setPermissionRequestHandler(handler)', () => {
|
||||
it('cancels any pending requests when cleared', async () => {
|
||||
const ses = w.webContents.session
|
||||
ses.setPermissionRequestHandler(() => {
|
||||
ses.setPermissionRequestHandler(null)
|
||||
})
|
||||
|
||||
const result = emittedOnce(require('electron').ipcMain, 'message')
|
||||
|
||||
function remote() {
|
||||
navigator.requestMIDIAccess({sysex: true}).then(() => {}, (err) => {
|
||||
require('electron').ipcRenderer.send('message', err.name);
|
||||
});
|
||||
}
|
||||
|
||||
await w.loadURL(`data:text/html,<script>(${remote})()</script>`)
|
||||
|
||||
const [,name] = await result
|
||||
expect(name).to.deep.equal('SecurityError')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
const chai = require('chai')
|
||||
const http = require('http')
|
||||
const https = require('https')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const send = require('send')
|
||||
const auth = require('basic-auth')
|
||||
const ChildProcess = require('child_process')
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
|
||||
const { ipcRenderer, remote } = require('electron')
|
||||
const { ipcMain, session, BrowserWindow, net } = remote
|
||||
const { expect } = chai
|
||||
|
||||
/* The whole session API doesn't use standard callbacks */
|
||||
/* eslint-disable standard/no-callback-literal */
|
||||
|
||||
describe('session module', () => {
|
||||
const fixtures = path.resolve(__dirname, 'fixtures')
|
||||
let w = null
|
||||
let webview = null
|
||||
const url = 'http://127.0.0.1'
|
||||
|
||||
beforeEach(() => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
width: 400,
|
||||
height: 400,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (webview != null) {
|
||||
if (!document.body.contains(webview)) {
|
||||
document.body.appendChild(webview)
|
||||
}
|
||||
webview.remove()
|
||||
}
|
||||
|
||||
return closeWindow(w).then(() => { w = null })
|
||||
})
|
||||
|
||||
describe('DownloadItem', () => {
|
||||
const mockPDF = Buffer.alloc(1024 * 1024 * 5)
|
||||
const protocolName = 'custom-dl'
|
||||
let contentDisposition = 'inline; filename="mock.pdf"'
|
||||
const downloadFilePath = path.join(fixtures, 'mock.pdf')
|
||||
const downloadServer = http.createServer((req, res) => {
|
||||
if (req.url === '/?testFilename') contentDisposition = 'inline'
|
||||
res.writeHead(200, {
|
||||
'Content-Length': mockPDF.length,
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': contentDisposition
|
||||
})
|
||||
res.end(mockPDF)
|
||||
downloadServer.close()
|
||||
})
|
||||
|
||||
const isPathEqual = (path1, path2) => {
|
||||
return path.relative(path1, path2) === ''
|
||||
}
|
||||
const assertDownload = (event, state, url, mimeType,
|
||||
receivedBytes, totalBytes, disposition,
|
||||
filename, port, savePath, isCustom) => {
|
||||
expect(state).to.equal('completed')
|
||||
expect(filename).to.equal('mock.pdf')
|
||||
expect(path.isAbsolute(savePath)).to.be.true()
|
||||
expect(isPathEqual(savePath, path.join(__dirname, 'fixtures', 'mock.pdf'))).to.be.true()
|
||||
if (isCustom) {
|
||||
expect(url).to.be.equal(`${protocolName}://item`)
|
||||
} else {
|
||||
expect(url).to.be.equal(`http://127.0.0.1:${port}/`)
|
||||
}
|
||||
expect(mimeType).to.equal('application/pdf')
|
||||
expect(receivedBytes).to.equal(mockPDF.length)
|
||||
expect(totalBytes).to.equal(mockPDF.length)
|
||||
expect(disposition).to.equal(contentDisposition)
|
||||
expect(fs.existsSync(downloadFilePath)).to.be.true()
|
||||
fs.unlinkSync(downloadFilePath)
|
||||
}
|
||||
|
||||
it('can download using WebContents.downloadURL', (done) => {
|
||||
downloadServer.listen(0, '127.0.0.1', () => {
|
||||
const port = downloadServer.address().port
|
||||
ipcRenderer.sendSync('set-download-option', false, false)
|
||||
w.webContents.downloadURL(`${url}:${port}`)
|
||||
ipcRenderer.once('download-done', (event, state, url,
|
||||
mimeType, receivedBytes,
|
||||
totalBytes, disposition,
|
||||
filename, savePath) => {
|
||||
assertDownload(event, state, url, mimeType, receivedBytes,
|
||||
totalBytes, disposition, filename, port, savePath)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('can download from custom protocols using WebContents.downloadURL', (done) => {
|
||||
const protocol = session.defaultSession.protocol
|
||||
downloadServer.listen(0, '127.0.0.1', () => {
|
||||
const port = downloadServer.address().port
|
||||
const handler = (ignoredError, callback) => {
|
||||
callback({ url: `${url}:${port}` })
|
||||
}
|
||||
protocol.registerHttpProtocol(protocolName, handler, (error) => {
|
||||
if (error) return done(error)
|
||||
ipcRenderer.sendSync('set-download-option', false, false)
|
||||
w.webContents.downloadURL(`${protocolName}://item`)
|
||||
ipcRenderer.once('download-done', (event, state, url,
|
||||
mimeType, receivedBytes,
|
||||
totalBytes, disposition,
|
||||
filename, savePath) => {
|
||||
assertDownload(event, state, url, mimeType, receivedBytes,
|
||||
totalBytes, disposition, filename, port, savePath,
|
||||
true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('can download using WebView.downloadURL', (done) => {
|
||||
downloadServer.listen(0, '127.0.0.1', () => {
|
||||
const port = downloadServer.address().port
|
||||
ipcRenderer.sendSync('set-download-option', false, false)
|
||||
webview = new WebView()
|
||||
webview.addEventListener('did-finish-load', () => {
|
||||
webview.downloadURL(`${url}:${port}/`)
|
||||
})
|
||||
webview.src = `file://${fixtures}/api/blank.html`
|
||||
ipcRenderer.once('download-done', (event, state, url,
|
||||
mimeType, receivedBytes,
|
||||
totalBytes, disposition,
|
||||
filename, savePath) => {
|
||||
assertDownload(event, state, url, mimeType, receivedBytes,
|
||||
totalBytes, disposition, filename, port, savePath)
|
||||
document.body.removeChild(webview)
|
||||
done()
|
||||
})
|
||||
document.body.appendChild(webview)
|
||||
})
|
||||
})
|
||||
|
||||
it('can cancel download', (done) => {
|
||||
downloadServer.listen(0, '127.0.0.1', () => {
|
||||
const port = downloadServer.address().port
|
||||
ipcRenderer.sendSync('set-download-option', true, false)
|
||||
w.webContents.downloadURL(`${url}:${port}/`)
|
||||
ipcRenderer.once('download-done', (event, state, url,
|
||||
mimeType, receivedBytes,
|
||||
totalBytes, disposition,
|
||||
filename) => {
|
||||
expect(state).to.equal('cancelled')
|
||||
expect(filename).to.equal('mock.pdf')
|
||||
expect(mimeType).to.equal('application/pdf')
|
||||
expect(receivedBytes).to.equal(0)
|
||||
expect(totalBytes).to.equal(mockPDF.length)
|
||||
expect(disposition).to.equal(contentDisposition)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate a default filename', function (done) {
|
||||
if (process.env.APPVEYOR === 'True') {
|
||||
// FIXME(alexeykuzmin): Skip the test.
|
||||
// this.skip()
|
||||
return done()
|
||||
}
|
||||
|
||||
downloadServer.listen(0, '127.0.0.1', () => {
|
||||
const port = downloadServer.address().port
|
||||
ipcRenderer.sendSync('set-download-option', true, false)
|
||||
w.webContents.downloadURL(`${url}:${port}/?testFilename`)
|
||||
ipcRenderer.once('download-done', (event, state, url,
|
||||
mimeType, receivedBytes,
|
||||
totalBytes, disposition,
|
||||
filename) => {
|
||||
expect(state).to.equal('cancelled')
|
||||
expect(filename).to.equal('download.pdf')
|
||||
expect(mimeType).to.equal('application/pdf')
|
||||
expect(receivedBytes).to.equal(0)
|
||||
expect(totalBytes).to.equal(mockPDF.length)
|
||||
expect(disposition).to.equal(contentDisposition)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('can set options for the save dialog', (done) => {
|
||||
downloadServer.listen(0, '127.0.0.1', () => {
|
||||
const filePath = path.join(__dirname, 'fixtures', 'mock.pdf')
|
||||
const port = downloadServer.address().port
|
||||
const options = {
|
||||
window: null,
|
||||
title: 'title',
|
||||
message: 'message',
|
||||
buttonLabel: 'buttonLabel',
|
||||
nameFieldLabel: 'nameFieldLabel',
|
||||
defaultPath: '/',
|
||||
filters: [{
|
||||
name: '1', extensions: ['.1', '.2']
|
||||
}, {
|
||||
name: '2', extensions: ['.3', '.4', '.5']
|
||||
}],
|
||||
showsTagField: true,
|
||||
securityScopedBookmarks: true
|
||||
}
|
||||
|
||||
ipcRenderer.sendSync('set-download-option', true, false, filePath, options)
|
||||
w.webContents.downloadURL(`${url}:${port}`)
|
||||
ipcRenderer.once('download-done', (event, state, url,
|
||||
mimeType, receivedBytes,
|
||||
totalBytes, disposition,
|
||||
filename, savePath, dialogOptions) => {
|
||||
expect(dialogOptions).to.deep.equal(options)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a save path is specified and the URL is unavailable', () => {
|
||||
it('does not display a save dialog and reports the done state as interrupted', (done) => {
|
||||
ipcRenderer.sendSync('set-download-option', false, false)
|
||||
ipcRenderer.once('download-done', (event, state) => {
|
||||
expect(state).to.equal('interrupted')
|
||||
done()
|
||||
})
|
||||
w.webContents.downloadURL(`file://${path.join(__dirname, 'does-not-exist.txt')}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('ses.createInterruptedDownload(options)', () => {
|
||||
it('can create an interrupted download item', (done) => {
|
||||
ipcRenderer.sendSync('set-download-option', true, false)
|
||||
const filePath = path.join(__dirname, 'fixtures', 'mock.pdf')
|
||||
const options = {
|
||||
path: filePath,
|
||||
urlChain: ['http://127.0.0.1/'],
|
||||
mimeType: 'application/pdf',
|
||||
offset: 0,
|
||||
length: 5242880
|
||||
}
|
||||
w.webContents.session.createInterruptedDownload(options)
|
||||
ipcRenderer.once('download-created', (event, state, urlChain,
|
||||
mimeType, receivedBytes,
|
||||
totalBytes, filename,
|
||||
savePath) => {
|
||||
expect(state).to.equal('interrupted')
|
||||
expect(urlChain).to.deep.equal(['http://127.0.0.1/'])
|
||||
expect(mimeType).to.equal('application/pdf')
|
||||
expect(receivedBytes).to.equal(0)
|
||||
expect(totalBytes).to.equal(5242880)
|
||||
expect(savePath).to.equal(filePath)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can be resumed', (done) => {
|
||||
const fixtures = path.join(__dirname, 'fixtures')
|
||||
const downloadFilePath = path.join(fixtures, 'logo.png')
|
||||
const rangeServer = http.createServer((req, res) => {
|
||||
const options = { root: fixtures }
|
||||
send(req, req.url, options)
|
||||
.on('error', (error) => { done(error) }).pipe(res)
|
||||
})
|
||||
ipcRenderer.sendSync('set-download-option', true, false, downloadFilePath)
|
||||
rangeServer.listen(0, '127.0.0.1', () => {
|
||||
const port = rangeServer.address().port
|
||||
const downloadUrl = `http://127.0.0.1:${port}/assets/logo.png`
|
||||
const callback = (event, state, url, mimeType,
|
||||
receivedBytes, totalBytes, disposition,
|
||||
filename, savePath, dialogOptions, urlChain,
|
||||
lastModifiedTime, eTag) => {
|
||||
if (state === 'cancelled') {
|
||||
const options = {
|
||||
path: savePath,
|
||||
urlChain: urlChain,
|
||||
mimeType: mimeType,
|
||||
offset: receivedBytes,
|
||||
length: totalBytes,
|
||||
lastModified: lastModifiedTime,
|
||||
eTag: eTag
|
||||
}
|
||||
ipcRenderer.sendSync('set-download-option', false, false, downloadFilePath)
|
||||
w.webContents.session.createInterruptedDownload(options)
|
||||
} else {
|
||||
expect(state).to.equal('completed')
|
||||
expect(filename).to.equal('logo.png')
|
||||
expect(savePath).to.equal(downloadFilePath)
|
||||
expect(url).to.equal(downloadUrl)
|
||||
expect(mimeType).to.equal('image/png')
|
||||
expect(receivedBytes).to.equal(14022)
|
||||
expect(totalBytes).to.equal(14022)
|
||||
expect(fs.existsSync(downloadFilePath)).to.be.true()
|
||||
fs.unlinkSync(downloadFilePath)
|
||||
rangeServer.close()
|
||||
ipcRenderer.removeListener('download-done', callback)
|
||||
done()
|
||||
}
|
||||
}
|
||||
ipcRenderer.on('download-done', callback)
|
||||
w.webContents.downloadURL(downloadUrl)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('ses.setPermissionRequestHandler(handler)', () => {
|
||||
it('cancels any pending requests when cleared', (done) => {
|
||||
const ses = session.fromPartition('permissionTest')
|
||||
ses.setPermissionRequestHandler(() => {
|
||||
ses.setPermissionRequestHandler(null)
|
||||
})
|
||||
|
||||
webview = new WebView()
|
||||
webview.addEventListener('ipc-message', (e) => {
|
||||
expect(e.channel).to.equal('message')
|
||||
expect(e.args).to.deep.equal(['SecurityError'])
|
||||
done()
|
||||
})
|
||||
webview.src = `file://${fixtures}/pages/permissions/midi-sysex.html`
|
||||
webview.partition = 'permissionTest'
|
||||
webview.setAttribute('nodeintegration', 'on')
|
||||
document.body.appendChild(webview)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -151,6 +151,20 @@ describe('systemPreferences module', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('systemPreferences.appLevelAppearance', () => {
|
||||
before(function () {
|
||||
if (process.platform !== 'darwin') this.skip()
|
||||
})
|
||||
|
||||
it('has an appLevelAppearance property', () => {
|
||||
expect(systemPreferences).to.have.a.property('appLevelAppearance')
|
||||
|
||||
// TODO(codebytere): remove when propertyification is complete
|
||||
expect(systemPreferences.getAppLevelAppearance).to.be.a('function')
|
||||
expect(systemPreferences.setAppLevelAppearance).to.be.a('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('systemPreferences.setUserDefault(key, type, value)', () => {
|
||||
before(function () {
|
||||
if (process.platform !== 'darwin') {
|
||||
|
||||
@@ -40,19 +40,19 @@ describe('webFrame module', function () {
|
||||
const spellCheckerFeedback =
|
||||
new Promise(resolve => {
|
||||
ipcMain.on('spec-spell-check', (e, words, callback) => {
|
||||
if (words.length === 2) {
|
||||
// The promise is resolved only after this event is received twice
|
||||
// Array contains only 1 word first time and 2 the next time
|
||||
if (words.length === 5) {
|
||||
// The API calls the provider after every completed word.
|
||||
// The promise is resolved only after this event is received with all words.
|
||||
resolve([words, callback])
|
||||
}
|
||||
})
|
||||
})
|
||||
const inputText = 'spleling test '
|
||||
const inputText = `spleling test you're `
|
||||
for (const keyCode of inputText) {
|
||||
w.webContents.sendInputEvent({ type: 'char', keyCode })
|
||||
}
|
||||
const [words, callback] = await spellCheckerFeedback
|
||||
expect(words).to.deep.equal(['spleling', 'test'])
|
||||
expect(words.sort()).to.deep.equal(['spleling', 'test', `you're`, 'you', 're'].sort())
|
||||
expect(callback).to.be.true()
|
||||
})
|
||||
|
||||
|
||||
@@ -1278,6 +1278,11 @@ describe('chromium feature', () => {
|
||||
|
||||
it('should download a pdf when plugins are disabled', (done) => {
|
||||
this.createBrowserWindow({ plugins: false, preload: 'preload-pdf-loaded.js' })
|
||||
// NOTE(nornagon): this test has been skipped for ages, so there's no way
|
||||
// to refactor it confidently. The 'set-download-option' ipc was removed
|
||||
// around May 2019, so if you're working on the pdf viewer and arrive at
|
||||
// this test and want to know what 'set-download-option' did, look here:
|
||||
// https://github.com/electron/electron/blob/d87b3ead760ae2d20f2401a8dac4ce548f8cd5f5/spec/static/main.js#L164
|
||||
ipcRenderer.sendSync('set-download-option', false, false)
|
||||
ipcRenderer.once('download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) => {
|
||||
expect(state).to.equal('completed')
|
||||
|
||||
1
spec/fixtures/api/site-instance-overrides/index.html
vendored
Normal file
1
spec/fixtures/api/site-instance-overrides/index.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<html></html>
|
||||
33
spec/fixtures/api/site-instance-overrides/main.js
vendored
Normal file
33
spec/fixtures/api/site-instance-overrides/main.js
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
const { app, BrowserWindow, ipcMain } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
process.on('uncaughtException', (e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
app.allowRendererProcessReuse = JSON.parse(process.argv[2])
|
||||
|
||||
const pids = []
|
||||
let win
|
||||
|
||||
ipcMain.on('pid', (event, pid) => {
|
||||
pids.push(pid)
|
||||
if (pids.length === 2) {
|
||||
console.log(JSON.stringify(pids))
|
||||
if (win) win.close()
|
||||
app.quit()
|
||||
} else {
|
||||
if (win) win.reload()
|
||||
}
|
||||
})
|
||||
|
||||
app.whenReady().then(() => {
|
||||
win = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload: path.resolve(__dirname, 'preload.js')
|
||||
}
|
||||
})
|
||||
win.loadFile('index.html')
|
||||
})
|
||||
4
spec/fixtures/api/site-instance-overrides/package.json
vendored
Normal file
4
spec/fixtures/api/site-instance-overrides/package.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "site-instance-overrides",
|
||||
"main": "main.js"
|
||||
}
|
||||
3
spec/fixtures/api/site-instance-overrides/preload.js
vendored
Normal file
3
spec/fixtures/api/site-instance-overrides/preload.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
ipcRenderer.send('pid', process.pid)
|
||||
@@ -158,58 +158,6 @@ app.on('ready', function () {
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
// For session's download test, listen 'will-download' event in browser, and
|
||||
// reply the result to renderer for verifying
|
||||
const downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf')
|
||||
ipcMain.on('set-download-option', function (event, needCancel, preventDefault, filePath = downloadFilePath, dialogOptions = {}) {
|
||||
window.webContents.session.once('will-download', function (e, item) {
|
||||
window.webContents.send('download-created',
|
||||
item.getState(),
|
||||
item.getURLChain(),
|
||||
item.getMimeType(),
|
||||
item.getReceivedBytes(),
|
||||
item.getTotalBytes(),
|
||||
item.getFilename(),
|
||||
item.getSavePath())
|
||||
if (preventDefault) {
|
||||
e.preventDefault()
|
||||
const url = item.getURL()
|
||||
const filename = item.getFilename()
|
||||
setImmediate(function () {
|
||||
try {
|
||||
item.getURL()
|
||||
} catch (err) {
|
||||
window.webContents.send('download-error', url, filename, err.message)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (item.getState() === 'interrupted' && !needCancel) {
|
||||
item.resume()
|
||||
} else {
|
||||
item.setSavePath(filePath)
|
||||
item.setSaveDialogOptions(dialogOptions)
|
||||
}
|
||||
item.on('done', function (e, state) {
|
||||
window.webContents.send('download-done',
|
||||
state,
|
||||
item.getURL(),
|
||||
item.getMimeType(),
|
||||
item.getReceivedBytes(),
|
||||
item.getTotalBytes(),
|
||||
item.getContentDisposition(),
|
||||
item.getFilename(),
|
||||
item.getSavePath(),
|
||||
item.getSaveDialogOptions(),
|
||||
item.getURLChain(),
|
||||
item.getLastModifiedTime(),
|
||||
item.getETag())
|
||||
})
|
||||
if (needCancel) item.cancel()
|
||||
}
|
||||
})
|
||||
event.returnValue = 'done'
|
||||
})
|
||||
|
||||
ipcMain.on('prevent-next-input-event', (event, key, id) => {
|
||||
webContents.fromId(id).once('before-input-event', (event, input) => {
|
||||
if (key === input.key) event.preventDefault()
|
||||
|
||||
47
yarn.lock
47
yarn.lock
@@ -23,8 +23,9 @@
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@electron/docs-parser@^0.1.1":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@electron/docs-parser/-/docs-parser-0.1.5.tgz#e6c05ed200b4c155986fb0f46178dede71a5e078"
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@electron/docs-parser/-/docs-parser-0.1.6.tgz#7ed86586e0ebc4a5c63e7c112264357adc61d334"
|
||||
integrity sha512-WAV0xHx1HIflqvb6G01LLnpS9n3VzNF0vyfxYhbP3Ev2p+m8CODc2+9pCdzCmH457UYi8GsX/jnB23djTCxt7Q==
|
||||
dependencies:
|
||||
"@types/markdown-it" "^0.0.7"
|
||||
chai "^4.2.0"
|
||||
@@ -156,10 +157,12 @@
|
||||
"@types/linkify-it@*":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-2.1.0.tgz#ea3dd64c4805597311790b61e872cbd1ed2cd806"
|
||||
integrity sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw==
|
||||
|
||||
"@types/markdown-it@^0.0.7":
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.7.tgz#75070485a3d8ad11e7deb8287f4430be15bf4d39"
|
||||
integrity sha512-WyL6pa76ollQFQNEaLVa41ZUUvDvPY+qAUmlsphnrpL6I9p1m868b26FyeoOmo7X3/Ta/S9WKXcEYXUSHnxoVQ==
|
||||
dependencies:
|
||||
"@types/linkify-it" "*"
|
||||
|
||||
@@ -319,6 +322,7 @@ ansi-regex@^3.0.0:
|
||||
ansi-regex@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
@@ -327,6 +331,7 @@ ansi-styles@^2.2.1:
|
||||
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
@@ -359,6 +364,7 @@ are-we-there-yet@~1.1.2:
|
||||
argparse@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
|
||||
dependencies:
|
||||
sprintf-js "~1.0.2"
|
||||
|
||||
@@ -469,6 +475,7 @@ assert@^1.4.0:
|
||||
assertion-error@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
|
||||
|
||||
assign-symbols@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -876,6 +883,7 @@ ccount@^1.0.0:
|
||||
chai@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5"
|
||||
integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==
|
||||
dependencies:
|
||||
assertion-error "^1.1.0"
|
||||
check-error "^1.0.2"
|
||||
@@ -897,6 +905,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
@@ -929,6 +938,7 @@ chardet@^0.7.0:
|
||||
check-error@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
|
||||
|
||||
check-for-leaks@^1.0.2:
|
||||
version "1.2.0"
|
||||
@@ -1010,6 +1020,7 @@ cli-cursor@^1.0.2:
|
||||
cli-cursor@^2.0.0, cli-cursor@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
|
||||
integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
|
||||
dependencies:
|
||||
restore-cursor "^2.0.0"
|
||||
|
||||
@@ -1020,6 +1031,7 @@ cli-spinners@^0.1.2:
|
||||
cli-spinners@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.1.0.tgz#22c34b4d51f573240885b201efda4e4ec9fff3c7"
|
||||
integrity sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA==
|
||||
|
||||
cli-truncate@^0.2.1:
|
||||
version "0.2.1"
|
||||
@@ -1035,6 +1047,7 @@ cli-width@^2.0.0:
|
||||
clone@^1.0.2:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
|
||||
|
||||
co@3.1.0:
|
||||
version "3.1.0"
|
||||
@@ -1058,12 +1071,14 @@ collection-visit@^1.0.0:
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
colors@^1.1.2:
|
||||
version "1.3.3"
|
||||
@@ -1291,6 +1306,7 @@ dedent@^0.7.0:
|
||||
deep-eql@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
|
||||
integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==
|
||||
dependencies:
|
||||
type-detect "^4.0.0"
|
||||
|
||||
@@ -1309,6 +1325,7 @@ deepmerge@3.2.0:
|
||||
defaults@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
|
||||
integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
|
||||
dependencies:
|
||||
clone "^1.0.2"
|
||||
|
||||
@@ -1554,6 +1571,7 @@ ensure-posix-path@^1.0.0:
|
||||
entities@~1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
|
||||
|
||||
error-ex@^1.2.0, error-ex@^1.3.1:
|
||||
version "1.3.2"
|
||||
@@ -2262,6 +2280,7 @@ get-assigned-identifiers@^1.2.0:
|
||||
get-func-name@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||
integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
|
||||
|
||||
get-own-enumerable-property-symbols@^3.0.0:
|
||||
version "3.0.0"
|
||||
@@ -2416,6 +2435,7 @@ has-flag@^2.0.0:
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has-symbols@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -3117,6 +3137,7 @@ levn@^0.3.0, levn@~0.3.0:
|
||||
linkify-it@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db"
|
||||
integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==
|
||||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
@@ -3247,6 +3268,7 @@ locate-path@^3.0.0:
|
||||
lodash.camelcase@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
|
||||
|
||||
lodash.flatten@^4.4.0:
|
||||
version "4.4.0"
|
||||
@@ -3289,6 +3311,7 @@ log-symbols@^1.0.2:
|
||||
log-symbols@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
|
||||
integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
|
||||
@@ -3354,6 +3377,7 @@ markdown-extensions@^1.1.0:
|
||||
markdown-it@^8.4.2:
|
||||
version "8.4.2"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54"
|
||||
integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
entities "~1.1.1"
|
||||
@@ -3410,6 +3434,7 @@ mdast-util-to-string@^1.0.2:
|
||||
mdurl@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
|
||||
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
@@ -3502,6 +3527,7 @@ mime@1.4.1:
|
||||
mimic-fn@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
|
||||
integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
|
||||
|
||||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
version "1.0.1"
|
||||
@@ -3806,6 +3832,7 @@ onetime@^1.0.0:
|
||||
onetime@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
|
||||
integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
|
||||
dependencies:
|
||||
mimic-fn "^1.0.0"
|
||||
|
||||
@@ -3838,6 +3865,7 @@ ora@^0.2.3:
|
||||
ora@^3.0.0, ora@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318"
|
||||
integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
cli-cursor "^2.1.0"
|
||||
@@ -3993,6 +4021,7 @@ parse-json@^4.0.0:
|
||||
parse-ms@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d"
|
||||
integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==
|
||||
|
||||
parseurl@~1.3.2:
|
||||
version "1.3.3"
|
||||
@@ -4067,6 +4096,7 @@ path-type@^3.0.0:
|
||||
pathval@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
|
||||
integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
|
||||
|
||||
pbkdf2@^3.0.3:
|
||||
version "3.0.17"
|
||||
@@ -4198,6 +4228,7 @@ pretty-bytes@^1.0.2:
|
||||
pretty-ms@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-5.0.0.tgz#6133a8f55804b208e4728f6aa7bf01085e951e24"
|
||||
integrity sha512-94VRYjL9k33RzfKiGokPBPpsmloBYSf5Ri+Pq19zlsEcUKFob+admeXr5eFDRuPjFmEOcjJvPGdillYOJyvZ7Q==
|
||||
dependencies:
|
||||
parse-ms "^2.1.0"
|
||||
|
||||
@@ -5069,6 +5100,7 @@ restore-cursor@^1.0.1:
|
||||
restore-cursor@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
|
||||
integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
|
||||
dependencies:
|
||||
onetime "^2.0.0"
|
||||
signal-exit "^3.0.2"
|
||||
@@ -5260,6 +5292,7 @@ shx@^0.3.2:
|
||||
signal-exit@^3.0.0, signal-exit@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
||||
|
||||
simple-concat@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -5399,6 +5432,7 @@ split-string@^3.0.1, split-string@^3.0.2:
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.1"
|
||||
@@ -5581,6 +5615,7 @@ strip-ansi@^4.0.0:
|
||||
strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
||||
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
|
||||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
@@ -5604,10 +5639,6 @@ strip-indent@^1.0.1:
|
||||
dependencies:
|
||||
get-stdin "^4.0.1"
|
||||
|
||||
strip-indent@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
|
||||
|
||||
strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
@@ -5637,6 +5668,7 @@ supports-color@^4.1.0:
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
@@ -5898,6 +5930,7 @@ type-check@~0.3.2:
|
||||
type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
||||
type-fest@^0.4.1:
|
||||
version "0.4.1"
|
||||
@@ -5926,6 +5959,7 @@ typescript@~3.3.3333:
|
||||
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
||||
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
|
||||
|
||||
umd@^3.0.0:
|
||||
version "3.0.3"
|
||||
@@ -6207,6 +6241,7 @@ walk-sync@^0.3.2:
|
||||
wcwidth@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
|
||||
integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
|
||||
dependencies:
|
||||
defaults "^1.0.3"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user