blob: 39004847485e10088d9fd10841cd34765c19b27f [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/page/create_window.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/public/common/dom_storage/session_storage_namespace_id.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/frame/from_ad_state.h"
#include "third_party/blink/public/mojom/loader/request_context_frame_type.mojom-blink.h"
#include "third_party/blink/public/web/web_view_client.h"
#include "third_party/blink/public/web/web_window_features.h"
#include "third_party/blink/renderer/core/core_initializer.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/exported/web_view_impl.h"
#include "third_party/blink/renderer/core/frame/ad_tracker.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/frame_client.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/frame_load_request.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/number_parsing_options.h"
#include "third_party/blink/renderer/platform/wtf/text/string_to_number.h"
#include "third_party/blink/renderer/platform/wtf/text/string_view.h"
namespace blink {
// Though isspace() considers \t and \v to be whitespace, Win IE doesn't when
// parsing window features.
static bool IsWindowFeaturesSeparator(UChar c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' ||
c == ',' || c == '\f';
}
WebWindowFeatures GetWindowFeaturesFromString(const String& feature_string) {
WebWindowFeatures window_features;
// This code follows the HTML spec, specifically
// https://html.spec.whatwg.org/C/#concept-window-open-features-tokenize
if (feature_string.IsEmpty())
return window_features;
bool ui_features_were_disabled = false;
unsigned key_begin, key_end;
unsigned value_begin, value_end;
String buffer = feature_string.LowerASCII();
unsigned length = buffer.length();
for (unsigned i = 0; i < length;) {
// skip to first non-separator (start of key name), but don't skip
// past the end of the string
while (i < length && IsWindowFeaturesSeparator(buffer[i]))
i++;
key_begin = i;
// skip to first separator (end of key name), but don't skip past
// the end of the string
while (i < length && !IsWindowFeaturesSeparator(buffer[i]))
i++;
key_end = i;
SECURITY_DCHECK(i <= length);
// skip separators past the key name, except '=', and don't skip past
// the end of the string
while (i < length && buffer[i] != '=') {
if (buffer[i] == ',' || !IsWindowFeaturesSeparator(buffer[i]))
break;
i++;
}
if (i < length && IsWindowFeaturesSeparator(buffer[i])) {
// skip to first non-separator (start of value), but don't skip
// past a ',' or the end of the string.
while (i < length && IsWindowFeaturesSeparator(buffer[i])) {
if (buffer[i] == ',')
break;
i++;
}
value_begin = i;
SECURITY_DCHECK(i <= length);
// skip to first separator (end of value)
while (i < length && !IsWindowFeaturesSeparator(buffer[i]))
i++;
value_end = i;
SECURITY_DCHECK(i <= length);
} else {
// No value given.
value_begin = i;
value_end = i;
}
if (key_begin == key_end)
continue;
StringView key_string(buffer, key_begin, key_end - key_begin);
StringView value_string(buffer, value_begin, value_end - value_begin);
// Listing a key with no value is shorthand for key=yes
int value;
if (value_string.IsEmpty() || value_string == "yes") {
value = 1;
} else if (value_string.Is8Bit()) {
value = CharactersToInt(value_string.Characters8(), value_string.length(),
WTF::NumberParsingOptions::kLoose, nullptr);
} else {
value =
CharactersToInt(value_string.Characters16(), value_string.length(),
WTF::NumberParsingOptions::kLoose, nullptr);
}
if (!ui_features_were_disabled && key_string != "noopener" &&
key_string != "noreferrer") {
ui_features_were_disabled = true;
window_features.menu_bar_visible = false;
window_features.status_bar_visible = false;
window_features.tool_bar_visible = false;
window_features.scrollbars_visible = false;
}
if (key_string == "left" || key_string == "screenx") {
window_features.x_set = true;
window_features.x = value;
} else if (key_string == "top" || key_string == "screeny") {
window_features.y_set = true;
window_features.y = value;
} else if (key_string == "width" || key_string == "innerwidth") {
window_features.width_set = true;
window_features.width = value;
} else if (key_string == "height" || key_string == "innerheight") {
window_features.height_set = true;
window_features.height = value;
} else if (key_string == "menubar") {
window_features.menu_bar_visible = value;
} else if (key_string == "toolbar" || key_string == "location") {
window_features.tool_bar_visible |= static_cast<bool>(value);
} else if (key_string == "status") {
window_features.status_bar_visible = value;
} else if (key_string == "scrollbars") {
window_features.scrollbars_visible = value;
} else if (key_string == "resizable") {
window_features.resizable = value;
} else if (key_string == "noopener") {
window_features.noopener = value;
} else if (key_string == "noreferrer") {
window_features.noreferrer = value;
} else if (key_string == "background") {
window_features.background = true;
} else if (key_string == "persistent") {
window_features.persistent = true;
}
}
if (window_features.noreferrer)
window_features.noopener = true;
return window_features;
}
static void MaybeLogWindowOpen(LocalFrame& opener_frame) {
AdTracker* ad_tracker = opener_frame.GetAdTracker();
if (!ad_tracker)
return;
bool is_ad_subframe = opener_frame.IsAdSubframe();
bool is_ad_script_in_stack =
ad_tracker->IsAdScriptInStack(AdTracker::StackType::kBottomAndTop);
FromAdState state =
blink::GetFromAdState(is_ad_subframe, is_ad_script_in_stack);
// Log to UMA.
UMA_HISTOGRAM_ENUMERATION("Blink.WindowOpen.FromAdState", state);
// Log to UKM.
ukm::UkmRecorder* ukm_recorder = opener_frame.GetDocument()->UkmRecorder();
ukm::SourceId source_id = opener_frame.GetDocument()->UkmSourceID();
if (source_id != ukm::kInvalidSourceId) {
ukm::builders::AbusiveExperienceHeuristic_WindowOpen(source_id)
.SetFromAdSubframe(is_ad_subframe)
.SetFromAdScript(is_ad_script_in_stack)
.Record(ukm_recorder);
}
}
Frame* CreateNewWindow(LocalFrame& opener_frame,
FrameLoadRequest& request,
const AtomicString& frame_name) {
LocalDOMWindow& opener_window = *opener_frame.DomWindow();
DCHECK(request.GetResourceRequest().RequestorOrigin() ||
opener_window.Url().IsEmpty());
DCHECK_EQ(kNavigationPolicyCurrentTab, request.GetNavigationPolicy());
if (opener_window.document()->PageDismissalEventBeingDispatched() !=
Document::kNoDismissal) {
return nullptr;
}
request.SetFrameType(mojom::RequestContextFrameType::kAuxiliary);
const KURL& url = request.GetResourceRequest().Url();
auto* csp_for_world = opener_window.GetContentSecurityPolicyForCurrentWorld();
if (url.ProtocolIsJavaScript() && csp_for_world) {
String script_source = DecodeURLEscapeSequences(
url.GetString(), DecodeURLMode::kUTF8OrIsomorphic);
if (!csp_for_world->AllowInline(
ContentSecurityPolicy::InlineType::kNavigation,
nullptr /* element */, script_source, String() /* nonce */,
opener_window.Url(), OrdinalNumber())) {
return nullptr;
}
}
if (!opener_window.GetSecurityOrigin()->CanDisplay(url)) {
opener_window.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kSecurity,
mojom::blink::ConsoleMessageLevel::kError,
"Not allowed to load local resource: " + url.ElidedString()));
return nullptr;
}
const WebWindowFeatures& features = request.GetWindowFeatures();
request.SetNavigationPolicy(NavigationPolicyForCreateWindow(features));
probe::WindowOpen(&opener_window, url, frame_name, features,
LocalFrame::HasTransientUserActivation(&opener_frame));
// Sandboxed frames cannot open new auxiliary browsing contexts.
if (opener_window.IsSandboxed(
network::mojom::blink::WebSandboxFlags::kPopups)) {
// FIXME: This message should be moved off the console once a solution to
// https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
opener_window.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kSecurity,
mojom::blink::ConsoleMessageLevel::kError,
"Blocked opening '" + url.ElidedString() +
"' in a new window because the request was made in a sandboxed "
"frame whose 'allow-popups' permission is not set."));
return nullptr;
}
network::mojom::blink::WebSandboxFlags sandbox_flags =
opener_window.IsSandboxed(network::mojom::blink::WebSandboxFlags::
kPropagatesToAuxiliaryBrowsingContexts)
? opener_window.GetSandboxFlags()
: network::mojom::blink::WebSandboxFlags::kNone;
SessionStorageNamespaceId new_namespace_id =
AllocateSessionStorageNamespaceId();
Page* old_page = opener_frame.GetPage();
if (!features.noopener ||
base::FeatureList::IsEnabled(features::kCloneSessionStorageForNoOpener)) {
CoreInitializer::GetInstance().CloneSessionStorage(old_page,
new_namespace_id);
}
bool consumed_user_gesture = false;
Page* page = old_page->GetChromeClient().CreateWindow(
&opener_frame, request, frame_name, features, sandbox_flags,
new_namespace_id, consumed_user_gesture);
if (!page)
return nullptr;
if (page == old_page) {
Frame* frame = &opener_frame.Tree().Top();
if (!opener_frame.CanNavigate(*frame))
return nullptr;
if (!features.noopener)
frame->SetOpener(&opener_frame);
return frame;
}
DCHECK(page->MainFrame());
LocalFrame& frame = *To<LocalFrame>(page->MainFrame());
if (request.GetShouldSendReferrer() == kMaybeSendReferrer)
frame.DomWindow()->SetReferrerPolicy(opener_window.GetReferrerPolicy());
page->SetWindowFeatures(features);
frame.View()->SetCanHaveScrollbars(features.scrollbars_visible);
IntRect window_rect = page->GetChromeClient().RootWindowRect(frame);
if (features.x_set)
window_rect.SetX(features.x);
if (features.y_set)
window_rect.SetY(features.y);
if (features.width_set)
window_rect.SetWidth(features.width);
if (features.height_set)
window_rect.SetHeight(features.height);
IntRect rect = page->GetChromeClient().CalculateWindowRectWithAdjustment(
window_rect, frame, opener_frame);
page->GetChromeClient().Show(opener_frame.GetLocalFrameToken(),
request.GetNavigationPolicy(), rect,
consumed_user_gesture);
MaybeLogWindowOpen(opener_frame);
return &frame;
}
} // namespace blink