diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 3f928a566659f..1ea6d243db4de 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -781,8 +781,29 @@ void WebContents::RenderViewCreated(content::RenderViewHost* render_view_host) { impl->disable_hidden_ = !background_throttling_; } +void WebContents::RenderViewHostChanged(content::RenderViewHost* old_host, + content::RenderViewHost* new_host) { + currently_committed_process_id_ = new_host->GetProcess()->GetID(); +} + void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) { + // This event is necessary for tracking any states with respect to + // intermediate render view hosts aka speculative render view hosts. Currently + // used by object-registry.js to ref count remote objects. Emit("render-view-deleted", render_view_host->GetProcess()->GetID()); + + if (-1 == currently_committed_process_id_ || + render_view_host->GetProcess()->GetID() == + currently_committed_process_id_) { + currently_committed_process_id_ = -1; + + // When the RVH that has been deleted is the current RVH it means that the + // the web contents are being closed. This is communicated by this event. + // Currently tracked by guest-window-manager.js to destroy the + // BrowserWindow. + Emit("current-render-view-deleted", + render_view_host->GetProcess()->GetID()); + } } void WebContents::RenderProcessGone(base::TerminationStatus status) { diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 607ff3bfb2846..4f37e621bd4e5 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -402,6 +402,8 @@ class WebContents : public mate::TrackableObject, // content::WebContentsObserver: void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; void RenderViewCreated(content::RenderViewHost*) override; + void RenderViewHostChanged(content::RenderViewHost* old_host, + content::RenderViewHost* new_host) override; void RenderViewDeleted(content::RenderViewHost*) override; void RenderProcessGone(base::TerminationStatus status) override; void DocumentLoadedInFrame( @@ -530,6 +532,10 @@ class WebContents : public mate::TrackableObject, // Observers of this WebContents. base::ObserverList observers_; + // The ID of the process of the currently committed RenderViewHost. + // -1 means no speculative RVH has been committed yet. + int currently_committed_process_id_ = -1; + DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index ac2d384206664..900b37d8bb22a 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -179,7 +179,7 @@ AtomBrowserClient::~AtomBrowserClient() { content::WebContents* AtomBrowserClient::GetWebContentsFromProcessID( int process_id) { // If the process is a pending process, we should use the web contents - // for the frame host passed into OverrideSiteInstanceForNavigation. + // for the frame host passed into RegisterPendingProcess. if (base::ContainsKey(pending_processes_, process_id)) return pending_processes_[process_id]; @@ -188,24 +188,36 @@ content::WebContents* AtomBrowserClient::GetWebContentsFromProcessID( return WebContentsPreferences::GetWebContentsFromProcessID(process_id); } -bool AtomBrowserClient::ShouldCreateNewSiteInstance( - content::RenderFrameHost* render_frame_host, +bool AtomBrowserClient::ShouldForceNewSiteInstance( + content::RenderFrameHost* current_rfh, + content::RenderFrameHost* speculative_rfh, content::BrowserContext* browser_context, - content::SiteInstance* current_instance, - const GURL& url) { + const GURL& url, + bool has_response_started) const { if (url.SchemeIs(url::kJavaScriptScheme)) // "javacript:" scheme should always use same SiteInstance return false; + content::SiteInstance* current_instance = current_rfh->GetSiteInstance(); + content::SiteInstance* speculative_instance = + speculative_rfh ? speculative_rfh->GetSiteInstance() : nullptr; int process_id = current_instance->GetProcess()->GetID(); - if (!IsRendererSandboxed(process_id)) { - if (!RendererUsesNativeWindowOpen(process_id)) { - // non-sandboxed renderers without native window.open should always create - // a new SiteInstance - return true; - } - auto* web_contents = - content::WebContents::FromRenderFrameHost(render_frame_host); + if (NavigationWasRedirectedCrossSite(browser_context, current_instance, + speculative_instance, url, + has_response_started)) { + // Navigation was redirected. We can't force the current, speculative or a + // new unrelated site instance to be used. Delegate to the content layer. + return false; + } else if (IsRendererSandboxed(process_id)) { + // Renderer is sandboxed, delegate the decision to the content layer for all + // origins. + return false; + } else if (!RendererUsesNativeWindowOpen(process_id)) { + // non-sandboxed renderers without native window.open should always create + // a new SiteInstance + return true; + } else { + auto* web_contents = content::WebContents::FromRenderFrameHost(current_rfh); if (!ChildWebContentsTracker::FromWebContents(web_contents)) { // Root WebContents should always create new process to make sure // native addons are loaded correctly after reload / navigation. @@ -220,6 +232,26 @@ bool AtomBrowserClient::ShouldCreateNewSiteInstance( return !IsSameWebSite(browser_context, src_url, url); } +bool AtomBrowserClient::NavigationWasRedirectedCrossSite( + content::BrowserContext* browser_context, + content::SiteInstance* current_instance, + content::SiteInstance* speculative_instance, + const GURL& dest_url, + bool has_response_started) const { + bool navigation_was_redirected = false; + if (has_response_started) { + navigation_was_redirected = !IsSameWebSite( + browser_context, current_instance->GetSiteURL(), dest_url); + } else { + navigation_was_redirected = + speculative_instance && + !IsSameWebSite(browser_context, speculative_instance->GetSiteURL(), + dest_url); + } + + return navigation_was_redirected; +} + void AtomBrowserClient::AddProcessPreferences( int process_id, AtomBrowserClient::ProcessPreferences prefs) { @@ -232,29 +264,69 @@ void AtomBrowserClient::RemoveProcessPreferences(int process_id) { process_preferences_.erase(process_id); } -bool AtomBrowserClient::IsProcessObserved(int process_id) { +bool AtomBrowserClient::IsProcessObserved(int process_id) const { base::AutoLock auto_lock(process_preferences_lock_); return process_preferences_.find(process_id) != process_preferences_.end(); } -bool AtomBrowserClient::IsRendererSandboxed(int process_id) { +bool AtomBrowserClient::IsRendererSandboxed(int process_id) const { base::AutoLock auto_lock(process_preferences_lock_); auto it = process_preferences_.find(process_id); return it != process_preferences_.end() && it->second.sandbox; } -bool AtomBrowserClient::RendererUsesNativeWindowOpen(int process_id) { +bool AtomBrowserClient::RendererUsesNativeWindowOpen(int process_id) const { base::AutoLock auto_lock(process_preferences_lock_); auto it = process_preferences_.find(process_id); return it != process_preferences_.end() && it->second.native_window_open; } -bool AtomBrowserClient::RendererDisablesPopups(int process_id) { +bool AtomBrowserClient::RendererDisablesPopups(int process_id) const { base::AutoLock auto_lock(process_preferences_lock_); auto it = process_preferences_.find(process_id); return it != process_preferences_.end() && it->second.disable_popups; } +std::string AtomBrowserClient::GetAffinityPreference( + content::RenderFrameHost* rfh) const { + auto* web_contents = content::WebContents::FromRenderFrameHost(rfh); + auto* web_preferences = WebContentsPreferences::From(web_contents); + std::string affinity; + if (web_preferences && + web_preferences->GetPreference("affinity", &affinity) && + !affinity.empty()) { + affinity = base::ToLowerASCII(affinity); + } + + return affinity; +} + +content::SiteInstance* AtomBrowserClient::GetSiteInstanceFromAffinity( + content::BrowserContext* browser_context, + const GURL& url, + content::RenderFrameHost* rfh) const { + std::string affinity = GetAffinityPreference(rfh); + if (!affinity.empty()) { + auto iter = site_per_affinities_.find(affinity); + GURL dest_site = content::SiteInstance::GetSiteForURL(browser_context, url); + if (iter != site_per_affinities_.end() && + IsSameWebSite(browser_context, iter->second->GetSiteURL(), dest_site)) { + return iter->second; + } + } + + return nullptr; +} + +void AtomBrowserClient::ConsiderSiteInstanceForAffinity( + content::RenderFrameHost* rfh, + content::SiteInstance* site_instance) { + std::string affinity = GetAffinityPreference(rfh); + if (!affinity.empty()) { + site_per_affinities_[affinity] = site_instance; + } +} + void AtomBrowserClient::RenderProcessWillLaunch( content::RenderProcessHost* host, service_manager::mojom::ServiceRequest* service_request) { @@ -322,62 +394,59 @@ void AtomBrowserClient::OverrideWebkitPrefs(content::RenderViewHost* host, web_preferences->OverrideWebkitPrefs(prefs); } -void AtomBrowserClient::OverrideSiteInstanceForNavigation( - content::RenderFrameHost* rfh, +content::ContentBrowserClient::SiteInstanceForNavigationType +AtomBrowserClient::ShouldOverrideSiteInstanceForNavigation( + content::RenderFrameHost* current_rfh, + content::RenderFrameHost* speculative_rfh, content::BrowserContext* browser_context, const GURL& url, - bool has_request_started, - content::SiteInstance* candidate_instance, - content::SiteInstance** new_instance) { + bool has_response_started, + content::SiteInstance** affinity_site_instance) const { if (g_suppress_renderer_process_restart) { g_suppress_renderer_process_restart = false; - return; + return SiteInstanceForNavigationType::ASK_CHROMIUM; } - content::SiteInstance* current_instance = rfh->GetSiteInstance(); - if (!ShouldCreateNewSiteInstance(rfh, browser_context, current_instance, url)) - return; - // Do we have an affinity site to manage ? - auto* web_contents = content::WebContents::FromRenderFrameHost(rfh); - auto* web_preferences = WebContentsPreferences::From(web_contents); - std::string affinity; - if (web_preferences && - web_preferences->GetPreference("affinity", &affinity) && - !affinity.empty()) { - affinity = base::ToLowerASCII(affinity); - auto iter = site_per_affinities.find(affinity); - GURL dest_site = content::SiteInstance::GetSiteForURL(browser_context, url); - if (iter != site_per_affinities.end() && - IsSameWebSite(browser_context, iter->second->GetSiteURL(), dest_site)) { - *new_instance = iter->second; - } else { - site_per_affinities[affinity] = candidate_instance; - *new_instance = candidate_instance; - // Remember the original web contents for the pending renderer process. - auto* pending_process = candidate_instance->GetProcess(); - pending_processes_[pending_process->GetID()] = web_contents; - } - } else { - // OverrideSiteInstanceForNavigation will be called more than once during a - // navigation (currently twice, on request and when it's about to commit in - // the renderer), look at RenderFrameHostManager::GetFrameHostForNavigation. - // In the default mode we should resuse the same site instance until the - // request commits otherwise it will get destroyed. Currently there is no - // unique lifetime tracker for a navigation request during site instance - // creation. We check for the state of the request, which should be one of - // (WAITING_FOR_RENDERER_RESPONSE, STARTED, RESPONSE_STARTED, FAILED) along - // with the availability of a speculative render frame host. - if (has_request_started) { - *new_instance = current_instance; - return; - } + content::SiteInstance* site_instance_from_affinity = + GetSiteInstanceFromAffinity(browser_context, url, current_rfh); + if (site_instance_from_affinity) { + *affinity_site_instance = site_instance_from_affinity; + return SiteInstanceForNavigationType::FORCE_AFFINITY; + } + + if (!ShouldForceNewSiteInstance(current_rfh, speculative_rfh, browser_context, + url, has_response_started)) { + return SiteInstanceForNavigationType::ASK_CHROMIUM; + } - *new_instance = candidate_instance; - // Remember the original web contents for the pending renderer process. - auto* pending_process = candidate_instance->GetProcess(); - pending_processes_[pending_process->GetID()] = web_contents; + // ShouldOverrideSiteInstanceForNavigation will be called more than once + // during a navigation (currently twice, on request and when it's about + // to commit in the renderer), look at + // RenderFrameHostManager::GetFrameHostForNavigation. + // In the default mode we should reuse the same site instance until the + // request commits otherwise it will get destroyed. Currently there is no + // unique lifetime tracker for a navigation request during site instance + // creation. We check for the state of the request, which should be one of + // (WAITING_FOR_RENDERER_RESPONSE, STARTED, RESPONSE_STARTED, FAILED) along + // with the availability of a speculative render frame host. + if (has_response_started) { + return SiteInstanceForNavigationType::FORCE_CURRENT; } + + return SiteInstanceForNavigationType::FORCE_CANDIDATE_OR_NEW; +} + +void AtomBrowserClient::RegisterPendingSiteInstance( + content::RenderFrameHost* rfh, + content::SiteInstance* pending_site_instance) { + // Do we have an affinity site to manage? + ConsiderSiteInstanceForAffinity(rfh, pending_site_instance); + + // Remember the original web contents for the pending renderer process. + auto* web_contents = content::WebContents::FromRenderFrameHost(rfh); + auto* pending_process = pending_site_instance->GetProcess(); + pending_processes_[pending_process->GetID()] = web_contents; } void AtomBrowserClient::AppendExtraCommandLineSwitches( @@ -555,10 +624,10 @@ void AtomBrowserClient::SiteInstanceDeleting( content::SiteInstance* site_instance) { // We are storing weak_ptr, is it fundamental to maintain the map up-to-date // when an instance is destroyed. - for (auto iter = site_per_affinities.begin(); - iter != site_per_affinities.end(); ++iter) { + for (auto iter = site_per_affinities_.begin(); + iter != site_per_affinities_.end(); ++iter) { if (iter->second == site_instance) { - site_per_affinities.erase(iter); + site_per_affinities_.erase(iter); break; } } @@ -786,7 +855,7 @@ void AtomBrowserClient::OnNetworkServiceCreated( network_service); } -bool AtomBrowserClient::ShouldBypassCORB(int render_process_id) { +bool AtomBrowserClient::ShouldBypassCORB(int render_process_id) const { // This is called on the network thread. base::AutoLock auto_lock(process_preferences_lock_); auto it = process_preferences_.find(render_process_id); @@ -799,4 +868,9 @@ std::string AtomBrowserClient::GetApplicationLocale() { return *g_application_locale; } +bool AtomBrowserClient::ShouldEnableStrictSiteIsolation() { + // Enable site isolation. It is off by default in Chromium <= 69. + return true; +} + } // namespace atom diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index e70ba5986fbf8..20f84a51f7936 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -65,6 +65,9 @@ class AtomBrowserClient : public content::ContentBrowserClient, // content::ContentBrowserClient: std::string GetApplicationLocale() override; + // content::ContentBrowserClient: + bool ShouldEnableStrictSiteIsolation() override; + protected: void RenderProcessWillLaunch( content::RenderProcessHost* host, @@ -73,13 +76,16 @@ class AtomBrowserClient : public content::ContentBrowserClient, CreateSpeechRecognitionManagerDelegate() override; void OverrideWebkitPrefs(content::RenderViewHost* render_view_host, content::WebPreferences* prefs) override; - void OverrideSiteInstanceForNavigation( - content::RenderFrameHost* render_frame_host, + SiteInstanceForNavigationType ShouldOverrideSiteInstanceForNavigation( + content::RenderFrameHost* current_rfh, + content::RenderFrameHost* speculative_rfh, content::BrowserContext* browser_context, - const GURL& dest_url, + const GURL& url, bool has_request_started, - content::SiteInstance* candidate_instance, - content::SiteInstance** new_instance) override; + content::SiteInstance** affinity_site_instance) const override; + void RegisterPendingSiteInstance( + content::RenderFrameHost* render_frame_host, + content::SiteInstance* pending_site_instance) override; void AppendExtraCommandLineSwitches(base::CommandLine* command_line, int child_process_id) override; void DidCreatePpapiPlugin(content::BrowserPpapiHost* browser_host) override; @@ -144,7 +150,7 @@ class AtomBrowserClient : public content::ContentBrowserClient, GetSystemSharedURLLoaderFactory() override; void OnNetworkServiceCreated( network::mojom::NetworkService* network_service) override; - bool ShouldBypassCORB(int render_process_id) override; + bool ShouldBypassCORB(int render_process_id) const override; // content::RenderProcessHostObserver: void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; @@ -169,16 +175,30 @@ class AtomBrowserClient : public content::ContentBrowserClient, bool web_security = true; }; - bool ShouldCreateNewSiteInstance(content::RenderFrameHost* render_frame_host, - content::BrowserContext* browser_context, - content::SiteInstance* current_instance, - const GURL& dest_url); + bool ShouldForceNewSiteInstance(content::RenderFrameHost* current_rfh, + content::RenderFrameHost* speculative_rfh, + content::BrowserContext* browser_context, + const GURL& dest_url, + bool has_request_started) const; + bool NavigationWasRedirectedCrossSite( + content::BrowserContext* browser_context, + content::SiteInstance* current_instance, + content::SiteInstance* speculative_instance, + const GURL& dest_url, + bool has_request_started) const; void AddProcessPreferences(int process_id, ProcessPreferences prefs); void RemoveProcessPreferences(int process_id); - bool IsProcessObserved(int process_id); - bool IsRendererSandboxed(int process_id); - bool RendererUsesNativeWindowOpen(int process_id); - bool RendererDisablesPopups(int process_id); + bool IsProcessObserved(int process_id) const; + bool IsRendererSandboxed(int process_id) const; + bool RendererUsesNativeWindowOpen(int process_id) const; + bool RendererDisablesPopups(int process_id) const; + std::string GetAffinityPreference(content::RenderFrameHost* rfh) const; + content::SiteInstance* GetSiteInstanceFromAffinity( + content::BrowserContext* browser_context, + const GURL& url, + content::RenderFrameHost* rfh) const; + void ConsiderSiteInstanceForAffinity(content::RenderFrameHost* rfh, + content::SiteInstance* site_instance); // pending_render_process => web contents. std::map pending_processes_; @@ -186,7 +206,7 @@ class AtomBrowserClient : public content::ContentBrowserClient, std::map render_process_host_pids_; // list of site per affinity. weak_ptr to prevent instance locking - std::map site_per_affinities; + std::map site_per_affinities_; std::unique_ptr resource_dispatcher_host_delegate_; @@ -196,7 +216,7 @@ class AtomBrowserClient : public content::ContentBrowserClient, Delegate* delegate_ = nullptr; - base::Lock process_preferences_lock_; + mutable base::Lock process_preferences_lock_; std::map process_preferences_; DISALLOW_COPY_AND_ASSIGN(AtomBrowserClient); diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index 6a71e94ce4101..ffc1bf99dabba 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.js @@ -90,9 +90,9 @@ const setupGuest = function (embedder, frameName, guest, options) { } const closedByUser = function () { embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId) - embedder.removeListener('render-view-deleted', closedByEmbedder) + embedder.removeListener('current-render-view-deleted', closedByEmbedder) } - embedder.once('render-view-deleted', closedByEmbedder) + embedder.once('current-render-view-deleted', closedByEmbedder) guest.once('closed', closedByUser) if (frameName) { frameToGuest.set(frameName, guest) @@ -118,28 +118,12 @@ const createGuest = function (embedder, url, referrer, frameName, options, postD } guest = new BrowserWindow(options) - if (!options.webContents || url !== 'about:blank') { + if (!options.webContents) { // We should not call `loadURL` if the window was constructed from an - // existing webContents(window.open in a sandboxed renderer) and if the url - // is not 'about:blank'. + // existing webContents (window.open in a sandboxed renderer). // // Navigating to the url when creating the window from an existing - // webContents would not be necessary(it will navigate there anyway), but - // apparently there's a bug that allows the child window to be scripted by - // the opener, even when the child window is from another origin. - // - // That's why the second condition(url !== "about:blank") is required: to - // force `OverrideSiteInstanceForNavigation` to be called and consequently - // spawn a new renderer if the new window is targeting a different origin. - // - // If the URL is "about:blank", then it is very likely that the opener just - // wants to synchronously script the popup, for example: - // - // let popup = window.open() - // popup.document.body.write('

hello

') - // - // The above code would not work if a navigation to "about:blank" is done - // here, since the window would be cleared of all changes in the next tick. + // webContents is not necessary (it will navigate there anyway). const loadOptions = { httpReferrer: referrer } diff --git a/patches/common/chromium/cross_site_document_resource_handler.patch b/patches/common/chromium/cross_site_document_resource_handler.patch index 565d7b4f4acfa..de284a575a06a 100644 --- a/patches/common/chromium/cross_site_document_resource_handler.patch +++ b/patches/common/chromium/cross_site_document_resource_handler.patch @@ -22,14 +22,14 @@ index 907922701280b589bf11691342de0ec95cdec6a1..eaf8bac18f8e3a2735ce7ded60619909 } diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc -index bb54b89bef5c6f32e7b4a056336c85494e2a04de..f069dfc4d2823b22eb0d90d28bc20236aeeddd04 100644 +index f713d0cfbf90665d921f56f4d828887ad1f7842c..4fe21468aee93a7cb3783220ebfe8dd100a3d1d5 100644 --- a/content/public/browser/content_browser_client.cc +++ b/content/public/browser/content_browser_client.cc -@@ -47,6 +47,10 @@ void OverrideOnBindInterface(const service_manager::BindSourceInfo& remote_info, - handle); +@@ -57,6 +57,10 @@ ContentBrowserClient::SiteInstanceForNavigationType ContentBrowserClient::Should + return SiteInstanceForNavigationType::ASK_CHROMIUM; } -+bool ContentBrowserClient::ShouldBypassCORB(int render_process_id) { ++bool ContentBrowserClient::ShouldBypassCORB(int render_process_id) const { + return false; +} + @@ -37,15 +37,15 @@ index bb54b89bef5c6f32e7b4a056336c85494e2a04de..f069dfc4d2823b22eb0d90d28bc20236 const MainFunctionParams& parameters) { return nullptr; diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h -index 2c22cb1cfe0dddc97c00e5f4ff89de6b18bc232f..a7b59095a887d566af9e74a646443fb49ea23bfc 100644 +index 4bf6b2b5f8110f539adc61858cfdc8f77f7ed08b..94454812e27d4d357eeee27cfc1e185386ea2003 100644 --- a/content/public/browser/content_browser_client.h +++ b/content/public/browser/content_browser_client.h -@@ -205,6 +205,9 @@ class CONTENT_EXPORT ContentBrowserClient { - SiteInstance* candidate_site_instance, - SiteInstance** new_instance) {} +@@ -225,6 +225,9 @@ class CONTENT_EXPORT ContentBrowserClient { + content::RenderFrameHost* rfh, + content::SiteInstance* pending_site_instance){}; + // Electron: Allows bypassing CORB checks for a renderer process. -+ virtual bool ShouldBypassCORB(int render_process_id); ++ 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 diff --git a/patches/common/chromium/frame_host_manager.patch b/patches/common/chromium/frame_host_manager.patch index 128aaf67b7dd0..d2ad29ae76281 100644 --- a/patches/common/chromium/frame_host_manager.patch +++ b/patches/common/chromium/frame_host_manager.patch @@ -7,10 +7,10 @@ Allows embedder to intercept site instances chosen by chromium and respond with custom instance. diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc -index 872e4609c94f1e052d623ae57c1279c72eb2c3f4..a59676004f2411631418bf12e2978623b9b27b53 100644 +index 872e4609c94f1e052d623ae57c1279c72eb2c3f4..39d26adb60c50f88d19e824846519338083dc166 100644 --- a/content/browser/frame_host/render_frame_host_manager.cc +++ b/content/browser/frame_host/render_frame_host_manager.cc -@@ -1960,6 +1960,18 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest( +@@ -1960,6 +1960,17 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest( bool was_server_redirect = request.navigation_handle() && request.navigation_handle()->WasServerRedirect(); @@ -23,13 +23,12 @@ index 872e4609c94f1e052d623ae57c1279c72eb2c3f4..a59676004f2411631418bf12e2978623 + scoped_refptr candidate_site_instance = + speculative_render_frame_host_ + ? speculative_render_frame_host_->GetSiteInstance() -+ : content::SiteInstance::CreateForURL(browser_context, -+ request.common_params().url); ++ : nullptr; + if (frame_tree_node_->IsMainFrame()) { // Renderer-initiated main frame navigations that may require a // SiteInstance swap are sent to the browser via the OpenURL IPC and are -@@ -1979,6 +1991,19 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest( +@@ -1979,6 +1990,51 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest( request.common_params().url)); no_renderer_swap_allowed |= request.from_begin_navigation() && !can_renderer_initiate_transfer; @@ -39,17 +38,49 @@ index 872e4609c94f1e052d623ae57c1279c72eb2c3f4..a59676004f2411631418bf12e2978623 + request.state() == NavigationRequest::FAILED) && + !speculative_render_frame_host_; + // Gives user a chance to choose a custom site instance. -+ SiteInstance* client_custom_instance = nullptr; -+ GetContentClient()->browser()->OverrideSiteInstanceForNavigation( -+ render_frame_host_.get(), browser_context, request.common_params().url, -+ has_response_started, candidate_site_instance.get(), -+ &client_custom_instance); -+ if (client_custom_instance) -+ return scoped_refptr(client_custom_instance); ++ SiteInstance* affinity_site_instance = nullptr; ++ scoped_refptr 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(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; ++ } } else { // Subframe navigations will use the current renderer, unless specifically // allowed to swap processes. -@@ -1990,18 +2015,9 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest( +@@ -1990,23 +2046,17 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest( if (no_renderer_swap_allowed) return scoped_refptr(current_site_instance); @@ -69,22 +100,73 @@ index 872e4609c94f1e052d623ae57c1279c72eb2c3f4..a59676004f2411631418bf12e2978623 request.common_params().transition, request.state() == NavigationRequest::FAILED, request.restore_type() != RestoreType::NONE, request.is_view_source(), + was_server_redirect); + ++ GetContentClient()->browser()->RegisterPendingSiteInstance( ++ render_frame_host_.get(), dest_site_instance.get()); ++ + return dest_site_instance; + } + +diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc +index bb54b89bef5c6f32e7b4a056336c85494e2a04de..f713d0cfbf90665d921f56f4d828887ad1f7842c 100644 +--- a/content/public/browser/content_browser_client.cc ++++ b/content/public/browser/content_browser_client.cc +@@ -47,6 +47,16 @@ void OverrideOnBindInterface(const service_manager::BindSourceInfo& remote_info, + handle); + } + ++ContentBrowserClient::SiteInstanceForNavigationType ContentBrowserClient::ShouldOverrideSiteInstanceForNavigation( ++ content::RenderFrameHost* current_rfh, ++ content::RenderFrameHost* speculative_rfh, ++ content::BrowserContext* browser_context, ++ const GURL& url, ++ bool has_request_started, ++ content::SiteInstance** affinity_site_instance) const { ++ return SiteInstanceForNavigationType::ASK_CHROMIUM; ++} ++ + BrowserMainParts* ContentBrowserClient::CreateBrowserMainParts( + const MainFunctionParams& parameters) { + return nullptr; diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h -index 3be31602689cb93b965729cc4e35cf6d23a8ec2f..2c22cb1cfe0dddc97c00e5f4ff89de6b18bc232f 100644 +index 3be31602689cb93b965729cc4e35cf6d23a8ec2f..4bf6b2b5f8110f539adc61858cfdc8f77f7ed08b 100644 --- a/content/public/browser/content_browser_client.h +++ b/content/public/browser/content_browser_client.h -@@ -196,6 +196,15 @@ class CONTENT_EXPORT ContentBrowserClient { +@@ -194,8 +194,37 @@ CONTENT_EXPORT void OverrideOnBindInterface( + // the observer interfaces.) + class CONTENT_EXPORT ContentBrowserClient { public: ++ // Identifies the type of site instance to use for a navigation. ++ enum SiteInstanceForNavigationType { ++ // Use either the candidate site instance or, if it doesn't exist ++ // a new, unrelated site instance for the navigation. ++ FORCE_CANDIDATE_OR_NEW = 0, ++ ++ // Use the current site instance for the navigation. ++ FORCE_CURRENT, ++ ++ // Use the provided affinity site instance for the navigation. ++ FORCE_AFFINITY, ++ ++ // Delegate the site instance creation to Chromium. ++ ASK_CHROMIUM ++ }; virtual ~ContentBrowserClient() {} + // Electron: Allows overriding the SiteInstance when navigating. -+ virtual void OverrideSiteInstanceForNavigation( -+ RenderFrameHost* render_frame_host, -+ BrowserContext* browser_context, -+ const GURL& dest_url, -+ bool has_response_started, -+ SiteInstance* candidate_site_instance, -+ SiteInstance** new_instance) {} ++ virtual SiteInstanceForNavigationType ShouldOverrideSiteInstanceForNavigation( ++ content::RenderFrameHost* current_rfh, ++ content::RenderFrameHost* speculative_rfh, ++ content::BrowserContext* browser_context, ++ const GURL& url, ++ bool has_request_started, ++ content::SiteInstance** affinity_site_instance) const; ++ ++ // Electron: Registers a pending site instance during a navigation. ++ virtual void RegisterPendingSiteInstance( ++ content::RenderFrameHost* rfh, ++ content::SiteInstance* pending_site_instance){}; + // Allows the embedder to set any number of custom BrowserMainParts // implementations for the browser startup code. See comments in diff --git a/spec/api-browser-window-affinity-spec.js b/spec/api-browser-window-affinity-spec.js index 21d6d769bcfad..2108b5f0e5162 100644 --- a/spec/api-browser-window-affinity-spec.js +++ b/spec/api-browser-window-affinity-spec.js @@ -26,67 +26,73 @@ describe('BrowserWindow with affinity module', () => { }) } - describe(`BrowserWindow with an affinity '${myAffinityName}'`, () => { - let mAffinityWindow - before(done => { - createWindowWithWebPrefs({ affinity: myAffinityName }) - .then((w) => { - mAffinityWindow = w + function testAffinityProcessIds (name, webPreferences = {}) { + describe(name, () => { + let mAffinityWindow + before(done => { + createWindowWithWebPrefs({ affinity: myAffinityName, ...webPreferences }) + .then((w) => { + mAffinityWindow = w + done() + }) + }) + + after(done => { + closeWindow(mAffinityWindow, { assertSingleWindow: false }).then(() => { + mAffinityWindow = null done() }) - }) - - after(done => { - closeWindow(mAffinityWindow, { assertSingleWindow: false }).then(() => { - mAffinityWindow = null - done() }) - }) - it('should have a different process id than a default window', done => { - createWindowWithWebPrefs({}) - .then(w => { - const affinityID = mAffinityWindow.webContents.getOSProcessId() - const wcID = w.webContents.getOSProcessId() + it('should have a different process id than a default window', done => { + createWindowWithWebPrefs({ ...webPreferences }) + .then(w => { + const affinityID = mAffinityWindow.webContents.getOSProcessId() + const wcID = w.webContents.getOSProcessId() - expect(affinityID).to.not.equal(wcID, 'Should have different OS process IDs') - closeWindow(w, { assertSingleWindow: false }).then(() => { done() }) - }) - }) + expect(affinityID).to.not.equal(wcID, 'Should have different OS process IDs') + closeWindow(w, { assertSingleWindow: false }).then(() => { done() }) + }) + }) - it(`should have a different process id than a window with a different affinity '${anotherAffinityName}'`, done => { - createWindowWithWebPrefs({ affinity: anotherAffinityName }) - .then(w => { - const affinityID = mAffinityWindow.webContents.getOSProcessId() - const wcID = w.webContents.getOSProcessId() + it(`should have a different process id than a window with a different affinity '${anotherAffinityName}'`, done => { + createWindowWithWebPrefs({ affinity: anotherAffinityName, ...webPreferences }) + .then(w => { + const affinityID = mAffinityWindow.webContents.getOSProcessId() + const wcID = w.webContents.getOSProcessId() - expect(affinityID).to.not.equal(wcID, 'Should have different OS process IDs') - closeWindow(w, { assertSingleWindow: false }).then(() => { done() }) - }) - }) + expect(affinityID).to.not.equal(wcID, 'Should have different OS process IDs') + closeWindow(w, { assertSingleWindow: false }).then(() => { done() }) + }) + }) - it(`should have the same OS process id than a window with the same affinity '${myAffinityName}'`, done => { - createWindowWithWebPrefs({ affinity: myAffinityName }) - .then(w => { - const affinityID = mAffinityWindow.webContents.getOSProcessId() - const wcID = w.webContents.getOSProcessId() + it(`should have the same OS process id than a window with the same affinity '${myAffinityName}'`, done => { + createWindowWithWebPrefs({ affinity: myAffinityName, ...webPreferences }) + .then(w => { + const affinityID = mAffinityWindow.webContents.getOSProcessId() + const wcID = w.webContents.getOSProcessId() - expect(affinityID).to.equal(wcID, 'Should have the same OS process ID') - closeWindow(w, { assertSingleWindow: false }).then(() => { done() }) - }) - }) + expect(affinityID).to.equal(wcID, 'Should have the same OS process ID') + closeWindow(w, { assertSingleWindow: false }).then(() => { done() }) + }) + }) - it(`should have the same OS process id than a window with an equivalent affinity '${myAffinityNameUpper}' (case insensitive)`, done => { - createWindowWithWebPrefs({ affinity: myAffinityNameUpper }) - .then(w => { - const affinityID = mAffinityWindow.webContents.getOSProcessId() - const wcID = w.webContents.getOSProcessId() + it(`should have the same OS process id than a window with an equivalent affinity '${myAffinityNameUpper}' (case insensitive)`, done => { + createWindowWithWebPrefs({ affinity: myAffinityNameUpper, ...webPreferences }) + .then(w => { + const affinityID = mAffinityWindow.webContents.getOSProcessId() + const wcID = w.webContents.getOSProcessId() - expect(affinityID).to.equal(wcID, 'Should have the same OS process ID') - closeWindow(w, { assertSingleWindow: false }).then(() => { done() }) - }) + expect(affinityID).to.equal(wcID, 'Should have the same OS process ID') + closeWindow(w, { assertSingleWindow: false }).then(() => { done() }) + }) + }) }) - }) + } + + testAffinityProcessIds(`BrowserWindow with an affinity '${myAffinityName}'`) + testAffinityProcessIds(`BrowserWindow with an affinity '${myAffinityName}' and sandbox enabled`, { sandbox: true }) + testAffinityProcessIds(`BrowserWindow with an affinity '${myAffinityName}' and nativeWindowOpen enabled`, { nativeWindowOpen: true }) describe(`BrowserWindow with an affinity : nodeIntegration=false`, () => { const preload = path.join(fixtures, 'module', 'send-later.js') diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index e816a0c9e70a6..b0b3d2a6d93eb 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -90,6 +90,8 @@ describe('BrowserWindow module', () => { res.end() } else if (req.url === '/navigate-302') { res.end(``) + } else if (req.url === '/cross-site') { + res.end(`

${req.url}

`) } else { res.end() } @@ -1470,29 +1472,6 @@ describe('BrowserWindow module', () => { const preload = path.join(fixtures, 'module', 'preload-sandbox.js') - // http protocol to simulate accessing another domain. This is required - // because the code paths for cross domain popups is different. - function crossDomainHandler (request, callback) { - // Disabled due to false positive in StandardJS - // eslint-disable-next-line standard/no-callback-literal - callback({ - mimeType: 'text/html', - data: `

${request.url}

` - }) - } - - before((done) => { - protocol.interceptStringProtocol('http', crossDomainHandler, () => { - done() - }) - }) - - after((done) => { - protocol.uninterceptProtocol('http', () => { - done() - }) - }) - it('exposes ipcRenderer to preload script', (done) => { ipcMain.once('answer', function (event, test) { assert.strictEqual(test, 'preload') @@ -1587,35 +1566,53 @@ describe('BrowserWindow module', () => { }) ipcRenderer.send('set-web-preferences-on-next-new-window', w.webContents.id, 'preload', preload) + const openerWindowOpen = emittedOnce(ipcMain, 'opener-loaded') + w.loadFile( + path.join(fixtures, 'api', 'sandbox.html'), + { search: 'window-open-external' } + ) + + // Wait for a message from the main window saying that it's ready. + await openerWindowOpen + + // Ask the opener to open a popup with window.opener. + const expectedPopupUrl = `${server.url}/cross-site` // Set in "sandbox.html". + const browserWindowCreated = emittedOnce(app, 'browser-window-created') - const childLoaded = emittedOnce(ipcMain, 'child-loaded') - w.loadFile(path.join(fixtures, 'api', 'sandbox.html'), { search: 'window-open-external' }) - const expectedPopupUrl = 'http://www.google.com/#q=electron' // Set in the "sandbox.html". + w.webContents.send('open-the-popup', expectedPopupUrl) // The page is going to open a popup that it won't be able to close. // We have to close it from here later. // XXX(alexeykuzmin): It will leak if the test fails too soon. const [, popupWindow] = await browserWindowCreated - // Wait for a message from the popup's preload script. - const [, openerIsNull, html, locationHref] = await childLoaded - expect(openerIsNull).to.be.true('window.opener is not null') - expect(html).to.equal(`

${expectedPopupUrl}

`, - 'looks like a http: request has not been intercepted locally') + // Ask the popup window for details. + const detailsAnswer = emittedOnce(ipcMain, 'child-loaded') + popupWindow.webContents.send('provide-details') + const [, openerIsNull, , locationHref] = await detailsAnswer + expect(openerIsNull).to.be.false('window.opener is null') expect(locationHref).to.equal(expectedPopupUrl) // Ask the page to access the popup. - const answer = emittedOnce(ipcMain, 'answer') + const touchPopupResult = emittedOnce(ipcMain, 'answer') w.webContents.send('touch-the-popup') - const [, exceptionMessage] = await answer + const [, popupAccessMessage] = await touchPopupResult + + // Ask the popup to access the opener. + const touchOpenerResult = emittedOnce(ipcMain, 'answer') + popupWindow.webContents.send('touch-the-opener') + const [, openerAccessMessage] = await touchOpenerResult // We don't need the popup anymore, and its parent page can't close it, // so let's close it from here before we run any checks. await closeWindow(popupWindow, { assertSingleWindow: false }) - expect(exceptionMessage).to.be.a('string', + expect(popupAccessMessage).to.be.a('string', `child's .document is accessible from its parent window`) - expect(exceptionMessage).to.match(/^Blocked a frame with origin/) + expect(popupAccessMessage).to.match(/^Blocked a frame with origin/) + expect(openerAccessMessage).to.be.a('string', + `opener .document is accessible from a popup window`) + expect(openerAccessMessage).to.match(/^Blocked a frame with origin/) }) it('should inherit the sandbox setting in opened windows', (done) => { diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index a3583bf67bcc3..83f5002e93ed6 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -712,6 +712,85 @@ describe('webContents module', () => { }) }) + describe('render view deleted events', () => { + let server = null + + before((done) => { + server = http.createServer((req, res) => { + const respond = () => { + if (req.url === '/redirect-cross-site') { + res.setHeader('Location', `${server.cross_site_url}/redirected`) + res.statusCode = 302 + res.end() + } else if (req.url === '/redirected') { + res.end('') + } else { + res.end() + } + } + setTimeout(respond, 0) + }) + server.listen(0, '127.0.0.1', () => { + server.url = `http://127.0.0.1:${server.address().port}` + server.cross_site_url = `http://localhost:${server.address().port}` + done() + }) + }) + + after(() => { + server.close() + server = null + }) + + it('does not emit current-render-view-deleted when speculative RVHs are deleted', (done) => { + let currentRenderViewDeletedEmitted = false + w.webContents.once('destroyed', () => { + assert.strictEqual(currentRenderViewDeletedEmitted, false, 'current-render-view-deleted was emitted') + done() + }) + const renderViewDeletedHandler = () => { + currentRenderViewDeletedEmitted = true + } + w.webContents.on('current-render-view-deleted', renderViewDeletedHandler) + w.webContents.on('did-finish-load', (e) => { + w.webContents.removeListener('current-render-view-deleted', renderViewDeletedHandler) + w.close() + }) + w.loadURL(`${server.url}/redirect-cross-site`) + }) + + it('emits current-render-view-deleted if the current RVHs are deleted', (done) => { + let currentRenderViewDeletedEmitted = false + w.webContents.once('destroyed', () => { + assert.strictEqual(currentRenderViewDeletedEmitted, true, 'current-render-view-deleted wasn\'t emitted') + done() + }) + w.webContents.on('current-render-view-deleted', () => { + currentRenderViewDeletedEmitted = true + }) + w.webContents.on('did-finish-load', (e) => { + w.close() + }) + w.loadURL(`${server.url}/redirect-cross-site`) + }) + + it('emits render-view-deleted if any RVHs are deleted', (done) => { + let rvhDeletedCount = 0 + w.webContents.once('destroyed', () => { + const expectedRenderViewDeletedEventCount = 3 // 1 speculative upon redirection + 2 upon window close. + assert.strictEqual(rvhDeletedCount, expectedRenderViewDeletedEventCount, 'render-view-deleted wasn\'t emitted the expected nr. of times') + done() + }) + w.webContents.on('render-view-deleted', () => { + rvhDeletedCount++ + }) + w.webContents.on('did-finish-load', (e) => { + w.close() + }) + w.loadURL(`${server.url}/redirect-cross-site`) + }) + }) + describe('setIgnoreMenuShortcuts(ignore)', () => { it('does not throw', () => { assert.strictEqual(w.webContents.setIgnoreMenuShortcuts(true), undefined) diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index 939b9c0fa8f40..9aeed38225f36 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -1016,16 +1016,20 @@ describe('chromium feature', () => { contents = null }) - // FIXME(deepak1556): Disabled with site isolation ON - // Localstorage area is accessed on the browser process - // before checking accessibility on the renderer side, - // causing illegal origin access renderer termination. - xit('cannot access localStorage', (done) => { - ipcMain.once('local-storage-response', (event, error) => { + it('cannot access localStorage', (done) => { + contents.on('crashed', (event, killed) => { + // Site isolation ON: process is killed for trying to access resources without permission. + if (process.platform !== 'win32') { + // Chromium on Windows does not set this flag correctly. + assert.strictEqual(killed, true, 'Process should\'ve been killed') + } + done() + }) + ipcMain.once('local-storage-response', (event, message) => { + // Site isolation OFF: access is refused. assert.strictEqual( - error, + message, 'Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.') - done() }) contents.loadURL(protocolName + '://host/localStorage') }) @@ -1066,6 +1070,59 @@ describe('chromium feature', () => { contents.loadURL(`${protocolName}://host/cookie`) }) }) + + describe('can be accessed', () => { + let server = null + before((done) => { + server = http.createServer((req, res) => { + const respond = () => { + if (req.url === '/redirect-cross-site') { + res.setHeader('Location', `${server.cross_site_url}/redirected`) + res.statusCode = 302 + res.end() + } else if (req.url === '/redirected') { + res.end('') + } else { + res.end() + } + } + setTimeout(respond, 0) + }) + server.listen(0, '127.0.0.1', () => { + server.url = `http://127.0.0.1:${server.address().port}` + server.cross_site_url = `http://localhost:${server.address().port}` + done() + }) + }) + + after(() => { + server.close() + server = null + }) + + const testLocalStorageAfterXSiteRedirect = (testTitle, extraPreferences = {}) => { + it(testTitle, (done) => { + const webPreferences = { show: false, ...extraPreferences } + w = new BrowserWindow(webPreferences) + let redirected = false + w.webContents.on('crashed', () => { + assert.fail('renderer crashed / was killed') + }) + w.webContents.on('did-redirect-navigation', (event, url) => { + assert.strictEqual(url, `${server.cross_site_url}/redirected`) + redirected = true + }) + w.webContents.on('did-finish-load', () => { + assert.strictEqual(redirected, true, 'didnt redirect') + done() + }) + w.loadURL(`${server.url}/redirect-cross-site`) + }) + } + + testLocalStorageAfterXSiteRedirect('after a cross-site redirect') + testLocalStorageAfterXSiteRedirect('after a cross-site redirect in sandbox mode', { sandbox: true }) + }) }) describe('websockets', () => { diff --git a/spec/fixtures/api/sandbox.html b/spec/fixtures/api/sandbox.html index 10263e0f57857..e997159d6ecb5 100644 --- a/spec/fixtures/api/sandbox.html +++ b/spec/fixtures/api/sandbox.html @@ -81,6 +81,9 @@ }, 'window-open-external': () => { addEventListener('load', () => { + ipcRenderer.once('open-the-popup', (event, url) => { + popup = open(url, '', 'top=65,left=55,width=505,height=605') + }) ipcRenderer.once('touch-the-popup', () => { let errorMessage = null try { @@ -90,7 +93,7 @@ } ipcRenderer.send('answer', errorMessage) }) - popup = open('http://www.google.com/#q=electron', '', 'top=65,left=55,width=505,height=605') + ipcRenderer.send('opener-loaded') }) }, 'verify-ipc-sender': () => { diff --git a/spec/fixtures/module/preload-sandbox.js b/spec/fixtures/module/preload-sandbox.js index 628f5c20270a7..92cba0b3eb6b1 100644 --- a/spec/fixtures/module/preload-sandbox.js +++ b/spec/fixtures/module/preload-sandbox.js @@ -22,7 +22,16 @@ } } else if (location.href !== 'about:blank') { addEventListener('DOMContentLoaded', () => { + ipcRenderer.on('touch-the-opener', () => { + let errorMessage = null + try { + const openerDoc = opener.document // eslint-disable-line no-unused-vars + } catch (error) { + errorMessage = error.message + } + ipcRenderer.send('answer', errorMessage) + }) ipcRenderer.send('child-loaded', window.opener == null, document.body.innerHTML, location.href) - }, false) + }) } })() diff --git a/spec/fixtures/pages/storage/local_storage.html b/spec/fixtures/pages/storage/local_storage.html index fc9bab0090849..523ef20851830 100644 --- a/spec/fixtures/pages/storage/local_storage.html +++ b/spec/fixtures/pages/storage/local_storage.html @@ -1,8 +1,11 @@