blob: 78c340d26e07205fa467ac93f068423ea23e99d7 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
* Copyright (C) 2015-2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 THE COPYRIGHT
* OWNER 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 "config.h"
#include "InspectorPageAgent.h"
#include "CachedResource.h"
#include "CachedResourceLoader.h"
#include "Cookie.h"
#include "CookieJar.h"
#include "DOMWrapperWorld.h"
#include "DocumentInlines.h"
#include "DocumentLoader.h"
#include "ElementInlines.h"
#include "Frame.h"
#include "FrameLoadRequest.h"
#include "FrameLoader.h"
#include "FrameSnapshotting.h"
#include "FrameView.h"
#include "HTMLFrameOwnerElement.h"
#include "HTMLNames.h"
#include "ImageBuffer.h"
#include "InspectorClient.h"
#include "InspectorDOMAgent.h"
#include "InspectorNetworkAgent.h"
#include "InspectorOverlay.h"
#include "InstrumentingAgents.h"
#include "MIMETypeRegistry.h"
#include "MemoryCache.h"
#include "Page.h"
#include "RenderObject.h"
#include "RenderTheme.h"
#include "ScriptController.h"
#include "ScriptSourceCode.h"
#include "SecurityOrigin.h"
#include "Settings.h"
#include "StyleScope.h"
#include <pal/text/TextEncoding.h>
#include "UserGestureIndicator.h"
#include <JavaScriptCore/ContentSearchUtilities.h>
#include <JavaScriptCore/IdentifiersFactory.h>
#include <JavaScriptCore/RegularExpression.h>
#include <wtf/ListHashSet.h>
#include <wtf/Stopwatch.h>
#include <wtf/text/Base64.h>
#include <wtf/text/StringBuilder.h>
#if ENABLE(APPLICATION_MANIFEST)
#include "CachedApplicationManifest.h"
#endif
#if ENABLE(WEB_ARCHIVE) && USE(CF)
#include "LegacyWebArchive.h"
#endif
namespace WebCore {
using namespace Inspector;
static bool decodeBuffer(const uint8_t* buffer, unsigned size, const String& textEncodingName, String* result)
{
if (buffer) {
PAL::TextEncoding encoding(textEncodingName);
if (!encoding.isValid())
encoding = PAL::WindowsLatin1Encoding();
*result = encoding.decode(buffer, size);
return true;
}
return false;
}
bool InspectorPageAgent::mainResourceContent(Frame* frame, bool withBase64Encode, String* result)
{
RefPtr<FragmentedSharedBuffer> buffer = frame->loader().documentLoader()->mainResourceData();
if (!buffer)
return false;
return InspectorPageAgent::dataContent(buffer->makeContiguous()->data(), buffer->size(), frame->document()->encoding(), withBase64Encode, result);
}
bool InspectorPageAgent::sharedBufferContent(RefPtr<FragmentedSharedBuffer>&& buffer, const String& textEncodingName, bool withBase64Encode, String* result)
{
return dataContent(buffer ? buffer->makeContiguous()->data() : nullptr, buffer ? buffer->size() : 0, textEncodingName, withBase64Encode, result);
}
bool InspectorPageAgent::dataContent(const uint8_t* data, unsigned size, const String& textEncodingName, bool withBase64Encode, String* result)
{
if (withBase64Encode) {
*result = base64EncodeToString(data, size);
return true;
}
return decodeBuffer(data, size, textEncodingName, result);
}
Vector<CachedResource*> InspectorPageAgent::cachedResourcesForFrame(Frame* frame)
{
Vector<CachedResource*> result;
for (auto& cachedResourceHandle : frame->document()->cachedResourceLoader().allCachedResources().values()) {
auto* cachedResource = cachedResourceHandle.get();
if (cachedResource->resourceRequest().hiddenFromInspector())
continue;
switch (cachedResource->type()) {
case CachedResource::Type::ImageResource:
// Skip images that were not auto loaded (images disabled in the user agent).
case CachedResource::Type::SVGFontResource:
case CachedResource::Type::FontResource:
// Skip fonts that were referenced in CSS but never used/downloaded.
if (cachedResource->stillNeedsLoad())
continue;
break;
default:
// All other CachedResource types download immediately.
break;
}
result.append(cachedResource);
}
return result;
}
void InspectorPageAgent::resourceContent(Protocol::ErrorString& errorString, Frame* frame, const URL& url, String* result, bool* base64Encoded)
{
DocumentLoader* loader = assertDocumentLoader(errorString, frame);
if (!loader)
return;
RefPtr<FragmentedSharedBuffer> buffer;
bool success = false;
if (equalIgnoringFragmentIdentifier(url, loader->url())) {
*base64Encoded = false;
success = mainResourceContent(frame, *base64Encoded, result);
}
if (!success) {
if (auto* resource = cachedResource(frame, url))
success = InspectorNetworkAgent::cachedResourceContent(*resource, result, base64Encoded);
}
if (!success)
errorString = "Missing resource for given url"_s;
}
String InspectorPageAgent::sourceMapURLForResource(CachedResource* cachedResource)
{
if (!cachedResource)
return String();
// Scripts are handled in a separate path.
if (cachedResource->type() != CachedResource::Type::CSSStyleSheet)
return String();
String sourceMapHeader = cachedResource->response().httpHeaderField(HTTPHeaderName::SourceMap);
if (!sourceMapHeader.isEmpty())
return sourceMapHeader;
sourceMapHeader = cachedResource->response().httpHeaderField(HTTPHeaderName::XSourceMap);
if (!sourceMapHeader.isEmpty())
return sourceMapHeader;
String content;
bool base64Encoded;
if (InspectorNetworkAgent::cachedResourceContent(*cachedResource, &content, &base64Encoded) && !base64Encoded)
return ContentSearchUtilities::findStylesheetSourceMapURL(content);
return String();
}
CachedResource* InspectorPageAgent::cachedResource(Frame* frame, const URL& url)
{
if (url.isNull())
return nullptr;
CachedResource* cachedResource = frame->document()->cachedResourceLoader().cachedResource(MemoryCache::removeFragmentIdentifierIfNeeded(url));
if (!cachedResource) {
ResourceRequest request(url);
request.setDomainForCachePartition(frame->document()->domainForCachePartition());
cachedResource = MemoryCache::singleton().resourceForRequest(request, frame->page()->sessionID());
}
return cachedResource;
}
Protocol::Page::ResourceType InspectorPageAgent::resourceTypeJSON(InspectorPageAgent::ResourceType resourceType)
{
switch (resourceType) {
case DocumentResource:
return Protocol::Page::ResourceType::Document;
case ImageResource:
return Protocol::Page::ResourceType::Image;
case FontResource:
return Protocol::Page::ResourceType::Font;
case StyleSheetResource:
return Protocol::Page::ResourceType::StyleSheet;
case ScriptResource:
return Protocol::Page::ResourceType::Script;
case XHRResource:
return Protocol::Page::ResourceType::XHR;
case FetchResource:
return Protocol::Page::ResourceType::Fetch;
case PingResource:
return Protocol::Page::ResourceType::Ping;
case BeaconResource:
return Protocol::Page::ResourceType::Beacon;
case WebSocketResource:
return Protocol::Page::ResourceType::WebSocket;
case EventSourceResource:
return Protocol::Page::ResourceType::EventSource;
case OtherResource:
return Protocol::Page::ResourceType::Other;
#if ENABLE(APPLICATION_MANIFEST)
case ApplicationManifestResource:
break;
#endif
}
return Protocol::Page::ResourceType::Other;
}
InspectorPageAgent::ResourceType InspectorPageAgent::inspectorResourceType(CachedResource::Type type)
{
switch (type) {
case CachedResource::Type::ImageResource:
return InspectorPageAgent::ImageResource;
case CachedResource::Type::SVGFontResource:
case CachedResource::Type::FontResource:
return InspectorPageAgent::FontResource;
#if ENABLE(XSLT)
case CachedResource::Type::XSLStyleSheet:
#endif
case CachedResource::Type::CSSStyleSheet:
return InspectorPageAgent::StyleSheetResource;
case CachedResource::Type::Script:
return InspectorPageAgent::ScriptResource;
case CachedResource::Type::MainResource:
return InspectorPageAgent::DocumentResource;
case CachedResource::Type::Beacon:
return InspectorPageAgent::BeaconResource;
#if ENABLE(APPLICATION_MANIFEST)
case CachedResource::Type::ApplicationManifest:
return InspectorPageAgent::ApplicationManifestResource;
#endif
case CachedResource::Type::Ping:
return InspectorPageAgent::PingResource;
case CachedResource::Type::MediaResource:
case CachedResource::Type::Icon:
case CachedResource::Type::RawResource:
default:
return InspectorPageAgent::OtherResource;
}
}
InspectorPageAgent::ResourceType InspectorPageAgent::inspectorResourceType(const CachedResource& cachedResource)
{
if (cachedResource.type() == CachedResource::Type::RawResource) {
switch (cachedResource.resourceRequest().requester()) {
case ResourceRequest::Requester::Fetch:
return InspectorPageAgent::FetchResource;
case ResourceRequest::Requester::Main:
return InspectorPageAgent::DocumentResource;
case ResourceRequest::Requester::EventSource:
return InspectorPageAgent::EventSourceResource;
default:
return InspectorPageAgent::XHRResource;
}
}
return inspectorResourceType(cachedResource.type());
}
Protocol::Page::ResourceType InspectorPageAgent::cachedResourceTypeJSON(const CachedResource& cachedResource)
{
return resourceTypeJSON(inspectorResourceType(cachedResource));
}
Frame* InspectorPageAgent::findFrameWithSecurityOrigin(Page& page, const String& originRawString)
{
for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (frame->document()->securityOrigin().toRawString() == originRawString)
return frame;
}
return nullptr;
}
DocumentLoader* InspectorPageAgent::assertDocumentLoader(Protocol::ErrorString& errorString, Frame* frame)
{
FrameLoader& frameLoader = frame->loader();
DocumentLoader* documentLoader = frameLoader.documentLoader();
if (!documentLoader)
errorString = "Missing document loader for given frame"_s;
return documentLoader;
}
InspectorPageAgent::InspectorPageAgent(PageAgentContext& context, InspectorClient* client, InspectorOverlay* overlay)
: InspectorAgentBase("Page"_s, context)
, m_frontendDispatcher(makeUnique<Inspector::PageFrontendDispatcher>(context.frontendRouter))
, m_backendDispatcher(Inspector::PageBackendDispatcher::create(context.backendDispatcher, this))
, m_inspectedPage(context.inspectedPage)
, m_client(client)
, m_overlay(overlay)
{
}
InspectorPageAgent::~InspectorPageAgent() = default;
void InspectorPageAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
{
}
void InspectorPageAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
{
disable();
}
Protocol::ErrorStringOr<void> InspectorPageAgent::enable()
{
if (m_instrumentingAgents.enabledPageAgent() == this)
return makeUnexpected("Page domain already enabled"_s);
m_instrumentingAgents.setEnabledPageAgent(this);
auto& stopwatch = m_environment.executionStopwatch();
stopwatch.reset();
stopwatch.start();
#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
defaultAppearanceDidChange(m_inspectedPage.defaultUseDarkAppearance());
#endif
return { };
}
Protocol::ErrorStringOr<void> InspectorPageAgent::disable()
{
m_instrumentingAgents.setEnabledPageAgent(nullptr);
setShowPaintRects(false);
#if !PLATFORM(IOS_FAMILY)
setShowRulers(false);
#endif
overrideUserAgent(nullString());
setEmulatedMedia(emptyString());
#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
setForcedAppearance(std::nullopt);
#endif
auto& inspectedPageSettings = m_inspectedPage.settings();
inspectedPageSettings.setAuthorAndUserStylesEnabledInspectorOverride(std::nullopt);
inspectedPageSettings.setICECandidateFilteringEnabledInspectorOverride(std::nullopt);
inspectedPageSettings.setImagesEnabledInspectorOverride(std::nullopt);
inspectedPageSettings.setMediaCaptureRequiresSecureConnectionInspectorOverride(std::nullopt);
inspectedPageSettings.setMockCaptureDevicesEnabledInspectorOverride(std::nullopt);
inspectedPageSettings.setNeedsSiteSpecificQuirksInspectorOverride(std::nullopt);
inspectedPageSettings.setScriptEnabledInspectorOverride(std::nullopt);
inspectedPageSettings.setShowDebugBordersInspectorOverride(std::nullopt);
inspectedPageSettings.setShowRepaintCounterInspectorOverride(std::nullopt);
inspectedPageSettings.setWebRTCEncryptionEnabledInspectorOverride(std::nullopt);
inspectedPageSettings.setWebSecurityEnabledInspectorOverride(std::nullopt);
m_client->setDeveloperPreferenceOverride(InspectorClient::DeveloperPreference::PrivateClickMeasurementDebugModeEnabled, std::nullopt);
m_client->setDeveloperPreferenceOverride(InspectorClient::DeveloperPreference::ITPDebugModeEnabled, std::nullopt);
m_client->setDeveloperPreferenceOverride(InspectorClient::DeveloperPreference::MockCaptureDevicesEnabled, std::nullopt);
return { };
}
double InspectorPageAgent::timestamp()
{
return m_environment.executionStopwatch().elapsedTime().seconds();
}
Protocol::ErrorStringOr<void> InspectorPageAgent::reload(std::optional<bool>&& ignoreCache, std::optional<bool>&& revalidateAllResources)
{
OptionSet<ReloadOption> reloadOptions;
if (ignoreCache && *ignoreCache)
reloadOptions.add(ReloadOption::FromOrigin);
if (!revalidateAllResources || !*revalidateAllResources)
reloadOptions.add(ReloadOption::ExpiredOnly);
m_inspectedPage.mainFrame().loader().reload(reloadOptions);
return { };
}
Protocol::ErrorStringOr<void> InspectorPageAgent::navigate(const String& url)
{
Frame& frame = m_inspectedPage.mainFrame();
UserGestureIndicator indicator { ProcessingUserGesture, frame.document() };
ResourceRequest resourceRequest { frame.document()->completeURL(url) };
FrameLoadRequest frameLoadRequest { *frame.document(), frame.document()->securityOrigin(), WTFMove(resourceRequest), selfTargetFrameName(), InitiatedByMainFrame::Unknown };
frameLoadRequest.disableNavigationToInvalidURL();
frame.loader().changeLocation(WTFMove(frameLoadRequest));
return { };
}
Protocol::ErrorStringOr<void> InspectorPageAgent::overrideUserAgent(const String& value)
{
m_userAgentOverride = value;
return { };
}
Protocol::ErrorStringOr<void> InspectorPageAgent::overrideSetting(Protocol::Page::Setting setting, std::optional<bool>&& value)
{
auto& inspectedPageSettings = m_inspectedPage.settings();
switch (setting) {
case Protocol::Page::Setting::PrivateClickMeasurementDebugModeEnabled:
m_client->setDeveloperPreferenceOverride(InspectorClient::DeveloperPreference::PrivateClickMeasurementDebugModeEnabled, value);
return { };
case Protocol::Page::Setting::AuthorAndUserStylesEnabled:
inspectedPageSettings.setAuthorAndUserStylesEnabledInspectorOverride(value);
return { };
case Protocol::Page::Setting::ICECandidateFilteringEnabled:
inspectedPageSettings.setICECandidateFilteringEnabledInspectorOverride(value);
return { };
case Protocol::Page::Setting::ITPDebugModeEnabled:
m_client->setDeveloperPreferenceOverride(InspectorClient::DeveloperPreference::ITPDebugModeEnabled, value);
return { };
case Protocol::Page::Setting::ImagesEnabled:
inspectedPageSettings.setImagesEnabledInspectorOverride(value);
return { };
case Protocol::Page::Setting::MediaCaptureRequiresSecureConnection:
inspectedPageSettings.setMediaCaptureRequiresSecureConnectionInspectorOverride(value);
return { };
case Protocol::Page::Setting::MockCaptureDevicesEnabled:
inspectedPageSettings.setMockCaptureDevicesEnabledInspectorOverride(value);
m_client->setDeveloperPreferenceOverride(InspectorClient::DeveloperPreference::MockCaptureDevicesEnabled, value);
return { };
case Protocol::Page::Setting::NeedsSiteSpecificQuirks:
inspectedPageSettings.setNeedsSiteSpecificQuirksInspectorOverride(value);
return { };
case Protocol::Page::Setting::ScriptEnabled:
inspectedPageSettings.setScriptEnabledInspectorOverride(value);
return { };
case Protocol::Page::Setting::ShowDebugBorders:
inspectedPageSettings.setShowDebugBordersInspectorOverride(value);
return { };
case Protocol::Page::Setting::ShowRepaintCounter:
inspectedPageSettings.setShowRepaintCounterInspectorOverride(value);
return { };
case Protocol::Page::Setting::WebRTCEncryptionEnabled:
inspectedPageSettings.setWebRTCEncryptionEnabledInspectorOverride(value);
return { };
case Protocol::Page::Setting::WebSecurityEnabled:
inspectedPageSettings.setWebSecurityEnabledInspectorOverride(value);
return { };
}
ASSERT_NOT_REACHED();
return { };
}
static Protocol::Page::CookieSameSitePolicy cookieSameSitePolicyJSON(Cookie::SameSitePolicy policy)
{
switch (policy) {
case Cookie::SameSitePolicy::None:
return Protocol::Page::CookieSameSitePolicy::None;
case Cookie::SameSitePolicy::Lax:
return Protocol::Page::CookieSameSitePolicy::Lax;
case Cookie::SameSitePolicy::Strict:
return Protocol::Page::CookieSameSitePolicy::Strict;
}
ASSERT_NOT_REACHED();
return Protocol::Page::CookieSameSitePolicy::None;
}
static Ref<Protocol::Page::Cookie> buildObjectForCookie(const Cookie& cookie)
{
return Protocol::Page::Cookie::create()
.setName(cookie.name)
.setValue(cookie.value)
.setDomain(cookie.domain)
.setPath(cookie.path)
.setExpires(cookie.expires.value_or(0))
.setSession(cookie.session)
.setHttpOnly(cookie.httpOnly)
.setSecure(cookie.secure)
.setSameSite(cookieSameSitePolicyJSON(cookie.sameSite))
.release();
}
static Ref<JSON::ArrayOf<Protocol::Page::Cookie>> buildArrayForCookies(ListHashSet<Cookie>& cookiesList)
{
auto cookies = JSON::ArrayOf<Protocol::Page::Cookie>::create();
for (const auto& cookie : cookiesList)
cookies->addItem(buildObjectForCookie(cookie));
return cookies;
}
static Vector<URL> allResourcesURLsForFrame(Frame* frame)
{
Vector<URL> result;
result.append(frame->loader().documentLoader()->url());
for (auto* cachedResource : InspectorPageAgent::cachedResourcesForFrame(frame))
result.append(cachedResource->url());
return result;
}
Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Protocol::Page::Cookie>>> InspectorPageAgent::getCookies()
{
ListHashSet<Cookie> allRawCookies;
for (Frame* frame = &m_inspectedPage.mainFrame(); frame; frame = frame->tree().traverseNext()) {
Document* document = frame->document();
if (!document || !document->page())
continue;
for (auto& url : allResourcesURLsForFrame(frame)) {
Vector<Cookie> rawCookiesForURLInDocument;
if (!document->page()->cookieJar().getRawCookies(*document, url, rawCookiesForURLInDocument))
continue;
for (auto& rawCookieForURLInDocument : rawCookiesForURLInDocument)
allRawCookies.add(rawCookieForURLInDocument);
}
}
return buildArrayForCookies(allRawCookies);
}
static std::optional<Cookie> parseCookieObject(Protocol::ErrorString& errorString, Ref<JSON::Object>&& cookieObject)
{
Cookie cookie;
cookie.name = cookieObject->getString(Protocol::Page::Cookie::nameKey);
if (!cookie.name) {
errorString = "Invalid value for key name in given cookie"_s;
return std::nullopt;
}
cookie.value = cookieObject->getString(Protocol::Page::Cookie::valueKey);
if (!cookie.value) {
errorString = "Invalid value for key value in given cookie"_s;
return std::nullopt;
}
cookie.domain = cookieObject->getString(Protocol::Page::Cookie::domainKey);
if (!cookie.domain) {
errorString = "Invalid value for key domain in given cookie"_s;
return std::nullopt;
}
cookie.path = cookieObject->getString(Protocol::Page::Cookie::pathKey);
if (!cookie.path) {
errorString = "Invalid value for key path in given cookie"_s;
return std::nullopt;
}
auto httpOnly = cookieObject->getBoolean(Protocol::Page::Cookie::httpOnlyKey);
if (!httpOnly) {
errorString = "Invalid value for key httpOnly in given cookie"_s;
return std::nullopt;
}
cookie.httpOnly = *httpOnly;
auto secure = cookieObject->getBoolean(Protocol::Page::Cookie::secureKey);
if (!secure) {
errorString = "Invalid value for key secure in given cookie"_s;
return std::nullopt;
}
cookie.secure = *secure;
auto session = cookieObject->getBoolean(Protocol::Page::Cookie::sessionKey);
cookie.expires = cookieObject->getDouble(Protocol::Page::Cookie::expiresKey);
if (!session && !cookie.expires) {
errorString = "Invalid value for key expires in given cookie"_s;
return std::nullopt;
}
cookie.session = *session;
auto sameSiteString = cookieObject->getString(Protocol::Page::Cookie::sameSiteKey);
if (!sameSiteString) {
errorString = "Invalid value for key sameSite in given cookie"_s;
return std::nullopt;
}
auto sameSite = Protocol::Helpers::parseEnumValueFromString<Protocol::Page::CookieSameSitePolicy>(sameSiteString);
if (!sameSite) {
errorString = "Invalid value for key sameSite in given cookie"_s;
return std::nullopt;
}
switch (sameSite.value()) {
case Protocol::Page::CookieSameSitePolicy::None:
cookie.sameSite = Cookie::SameSitePolicy::None;
break;
case Protocol::Page::CookieSameSitePolicy::Lax:
cookie.sameSite = Cookie::SameSitePolicy::Lax;
break;
case Protocol::Page::CookieSameSitePolicy::Strict:
cookie.sameSite = Cookie::SameSitePolicy::Strict;
break;
}
return cookie;
}
Protocol::ErrorStringOr<void> InspectorPageAgent::setCookie(Ref<JSON::Object>&& cookieObject)
{
Protocol::ErrorString errorString;
auto cookie = parseCookieObject(errorString, WTFMove(cookieObject));
if (!cookie)
return makeUnexpected(errorString);
for (auto* frame = &m_inspectedPage.mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (auto* document = frame->document()) {
if (auto* page = document->page())
page->cookieJar().setRawCookie(*document, cookie.value());
}
}
return { };
}
Protocol::ErrorStringOr<void> InspectorPageAgent::deleteCookie(const String& cookieName, const String& url)
{
URL parsedURL({ }, url);
for (Frame* frame = &m_inspectedPage.mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (auto* document = frame->document()) {
if (auto* page = document->page())
page->cookieJar().deleteCookie(*document, parsedURL, cookieName, [] { });
}
}
return { };
}
Protocol::ErrorStringOr<Ref<Protocol::Page::FrameResourceTree>> InspectorPageAgent::getResourceTree()
{
return buildObjectForFrameTree(&m_inspectedPage.mainFrame());
}
Protocol::ErrorStringOr<std::tuple<String, bool /* base64Encoded */>> InspectorPageAgent::getResourceContent(const Protocol::Network::FrameId& frameId, const String& url)
{
Protocol::ErrorString errorString;
Frame* frame = assertFrame(errorString, frameId);
if (!frame)
return makeUnexpected(errorString);
String content;
bool base64Encoded;
resourceContent(errorString, frame, URL({ }, url), &content, &base64Encoded);
return { { content, base64Encoded } };
}
Protocol::ErrorStringOr<void> InspectorPageAgent::setBootstrapScript(const String& source)
{
m_bootstrapScript = source;
return { };
}
Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Protocol::GenericTypes::SearchMatch>>> InspectorPageAgent::searchInResource(const Protocol::Network::FrameId& frameId, const String& url, const String& query, std::optional<bool>&& caseSensitive, std::optional<bool>&& isRegex, const Protocol::Network::RequestId& requestId)
{
Protocol::ErrorString errorString;
if (!!requestId) {
if (auto* networkAgent = m_instrumentingAgents.enabledNetworkAgent()) {
RefPtr<JSON::ArrayOf<Protocol::GenericTypes::SearchMatch>> result;
networkAgent->searchInRequest(errorString, requestId, query, caseSensitive && *caseSensitive, isRegex && *isRegex, result);
if (!result)
return makeUnexpected(errorString);
return result.releaseNonNull();
}
}
Frame* frame = assertFrame(errorString, frameId);
if (!frame)
return makeUnexpected(errorString);
DocumentLoader* loader = assertDocumentLoader(errorString, frame);
if (!loader)
return makeUnexpected(errorString);
URL kurl({ }, url);
String content;
bool success = false;
if (equalIgnoringFragmentIdentifier(kurl, loader->url()))
success = mainResourceContent(frame, false, &content);
if (!success) {
if (auto* resource = cachedResource(frame, kurl)) {
if (auto textContent = InspectorNetworkAgent::textContentForCachedResource(*resource)) {
content = *textContent;
success = true;
}
}
}
if (!success)
return JSON::ArrayOf<Protocol::GenericTypes::SearchMatch>::create();
return ContentSearchUtilities::searchInTextByLines(content, query, caseSensitive && *caseSensitive, isRegex && *isRegex);
}
static Ref<Protocol::Page::SearchResult> buildObjectForSearchResult(const Protocol::Network::FrameId& frameId, const String& url, int matchesCount)
{
return Protocol::Page::SearchResult::create()
.setUrl(url)
.setFrameId(frameId)
.setMatchesCount(matchesCount)
.release();
}
Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Protocol::Page::SearchResult>>> InspectorPageAgent::searchInResources(const String& text, std::optional<bool>&& caseSensitive, std::optional<bool>&& isRegex)
{
auto result = JSON::ArrayOf<Protocol::Page::SearchResult>::create();
auto searchStringType = (isRegex && *isRegex) ? ContentSearchUtilities::SearchStringType::Regex : ContentSearchUtilities::SearchStringType::ContainsString;
auto regex = ContentSearchUtilities::createRegularExpressionForSearchString(text, caseSensitive && *caseSensitive, searchStringType);
for (Frame* frame = &m_inspectedPage.mainFrame(); frame; frame = frame->tree().traverseNext()) {
for (auto* cachedResource : cachedResourcesForFrame(frame)) {
if (auto textContent = InspectorNetworkAgent::textContentForCachedResource(*cachedResource)) {
int matchesCount = ContentSearchUtilities::countRegularExpressionMatches(regex, *textContent);
if (matchesCount)
result->addItem(buildObjectForSearchResult(frameId(frame), cachedResource->url().string(), matchesCount));
}
}
}
if (auto* networkAgent = m_instrumentingAgents.enabledNetworkAgent())
networkAgent->searchOtherRequests(regex, result);
return result;
}
#if !PLATFORM(IOS_FAMILY)
Protocol::ErrorStringOr<void> InspectorPageAgent::setShowRulers(bool showRulers)
{
m_overlay->setShowRulers(showRulers);
return { };
}
#endif
Protocol::ErrorStringOr<void> InspectorPageAgent::setShowPaintRects(bool show)
{
m_showPaintRects = show;
m_client->setShowPaintRects(show);
if (m_client->overridesShowPaintRects())
return { };
m_overlay->setShowPaintRects(show);
return { };
}
void InspectorPageAgent::domContentEventFired()
{
m_isFirstLayoutAfterOnLoad = true;
m_frontendDispatcher->domContentEventFired(timestamp());
}
void InspectorPageAgent::loadEventFired()
{
m_frontendDispatcher->loadEventFired(timestamp());
}
void InspectorPageAgent::frameNavigated(Frame& frame)
{
m_frontendDispatcher->frameNavigated(buildObjectForFrame(&frame));
}
void InspectorPageAgent::frameDetached(Frame& frame)
{
auto identifier = m_frameToIdentifier.take(&frame);
if (identifier.isNull())
return;
m_frontendDispatcher->frameDetached(identifier);
m_identifierToFrame.remove(identifier);
}
Frame* InspectorPageAgent::frameForId(const Protocol::Network::FrameId& frameId)
{
return frameId.isEmpty() ? nullptr : m_identifierToFrame.get(frameId).get();
}
String InspectorPageAgent::frameId(Frame* frame)
{
if (!frame)
return emptyString();
return m_frameToIdentifier.ensure(frame, [this, frame] {
auto identifier = IdentifiersFactory::createIdentifier();
m_identifierToFrame.set(identifier, frame);
return identifier;
}).iterator->value;
}
String InspectorPageAgent::loaderId(DocumentLoader* loader)
{
if (!loader)
return emptyString();
return m_loaderToIdentifier.ensure(loader, [] {
return IdentifiersFactory::createIdentifier();
}).iterator->value;
}
Frame* InspectorPageAgent::assertFrame(Protocol::ErrorString& errorString, const Protocol::Network::FrameId& frameId)
{
Frame* frame = frameForId(frameId);
if (!frame)
errorString = "Missing frame for given frameId"_s;
return frame;
}
void InspectorPageAgent::loaderDetachedFromFrame(DocumentLoader& loader)
{
m_loaderToIdentifier.remove(&loader);
}
void InspectorPageAgent::frameStartedLoading(Frame& frame)
{
m_frontendDispatcher->frameStartedLoading(frameId(&frame));
}
void InspectorPageAgent::frameStoppedLoading(Frame& frame)
{
m_frontendDispatcher->frameStoppedLoading(frameId(&frame));
}
void InspectorPageAgent::frameScheduledNavigation(Frame& frame, Seconds delay)
{
m_frontendDispatcher->frameScheduledNavigation(frameId(&frame), delay.value());
}
void InspectorPageAgent::frameClearedScheduledNavigation(Frame& frame)
{
m_frontendDispatcher->frameClearedScheduledNavigation(frameId(&frame));
}
#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
void InspectorPageAgent::defaultAppearanceDidChange(bool useDarkAppearance)
{
m_frontendDispatcher->defaultAppearanceDidChange(useDarkAppearance ? Protocol::Page::Appearance::Dark : Protocol::Page::Appearance::Light);
}
#endif
void InspectorPageAgent::didClearWindowObjectInWorld(Frame& frame, DOMWrapperWorld& world)
{
if (&world != &mainThreadNormalWorld())
return;
if (m_bootstrapScript.isEmpty())
return;
frame.script().evaluateIgnoringException(ScriptSourceCode(m_bootstrapScript, URL { "web-inspector://bootstrap.js"_str }));
}
void InspectorPageAgent::didPaint(RenderObject& renderer, const LayoutRect& rect)
{
if (!m_showPaintRects)
return;
LayoutRect absoluteRect = LayoutRect(renderer.localToAbsoluteQuad(FloatRect(rect)).boundingBox());
FrameView* view = renderer.document().view();
LayoutRect rootRect = absoluteRect;
if (!view->frame().isMainFrame()) {
IntRect rootViewRect = view->contentsToRootView(snappedIntRect(absoluteRect));
rootRect = view->frame().mainFrame().view()->rootViewToContents(rootViewRect);
}
if (m_client->overridesShowPaintRects()) {
m_client->showPaintRect(rootRect);
return;
}
m_overlay->showPaintRect(rootRect);
}
void InspectorPageAgent::didLayout()
{
bool isFirstLayout = m_isFirstLayoutAfterOnLoad;
if (isFirstLayout)
m_isFirstLayoutAfterOnLoad = false;
m_overlay->update();
}
void InspectorPageAgent::didScroll()
{
m_overlay->update();
}
void InspectorPageAgent::didRecalculateStyle()
{
m_overlay->update();
}
Ref<Protocol::Page::Frame> InspectorPageAgent::buildObjectForFrame(Frame* frame)
{
ASSERT_ARG(frame, frame);
auto frameObject = Protocol::Page::Frame::create()
.setId(frameId(frame))
.setLoaderId(loaderId(frame->loader().documentLoader()))
.setUrl(frame->document()->url().string())
.setMimeType(frame->loader().documentLoader()->responseMIMEType())
.setSecurityOrigin(frame->document()->securityOrigin().toRawString())
.release();
if (frame->tree().parent())
frameObject->setParentId(frameId(frame->tree().parent()));
if (frame->ownerElement()) {
String name = frame->ownerElement()->getNameAttribute();
if (name.isEmpty())
name = frame->ownerElement()->attributeWithoutSynchronization(HTMLNames::idAttr);
frameObject->setName(name);
}
return frameObject;
}
Ref<Protocol::Page::FrameResourceTree> InspectorPageAgent::buildObjectForFrameTree(Frame* frame)
{
ASSERT_ARG(frame, frame);
auto frameObject = buildObjectForFrame(frame);
auto subresources = JSON::ArrayOf<Protocol::Page::FrameResource>::create();
auto result = Protocol::Page::FrameResourceTree::create()
.setFrame(WTFMove(frameObject))
.setResources(subresources.copyRef())
.release();
for (auto* cachedResource : cachedResourcesForFrame(frame)) {
auto resourceObject = Protocol::Page::FrameResource::create()
.setUrl(cachedResource->url().string())
.setType(cachedResourceTypeJSON(*cachedResource))
.setMimeType(cachedResource->response().mimeType())
.release();
if (cachedResource->wasCanceled())
resourceObject->setCanceled(true);
else if (cachedResource->status() == CachedResource::LoadError || cachedResource->status() == CachedResource::DecodeError)
resourceObject->setFailed(true);
String sourceMappingURL = InspectorPageAgent::sourceMapURLForResource(cachedResource);
if (!sourceMappingURL.isEmpty())
resourceObject->setSourceMapURL(sourceMappingURL);
String targetId = cachedResource->resourceRequest().initiatorIdentifier();
if (!targetId.isEmpty())
resourceObject->setTargetId(targetId);
subresources->addItem(WTFMove(resourceObject));
}
RefPtr<JSON::ArrayOf<Protocol::Page::FrameResourceTree>> childrenArray;
for (Frame* child = frame->tree().firstChild(); child; child = child->tree().nextSibling()) {
if (!childrenArray) {
childrenArray = JSON::ArrayOf<Protocol::Page::FrameResourceTree>::create();
result->setChildFrames(*childrenArray);
}
childrenArray->addItem(buildObjectForFrameTree(child));
}
return result;
}
Protocol::ErrorStringOr<void> InspectorPageAgent::setEmulatedMedia(const String& media)
{
if (media == m_emulatedMedia)
return { };
m_emulatedMedia = media;
// FIXME: Schedule a rendering update instead of synchronously updating the layout.
m_inspectedPage.updateStyleAfterChangeInEnvironment();
RefPtr document = m_inspectedPage.mainFrame().document();
if (!document)
return { };
document->updateLayout();
document->evaluateMediaQueriesAndReportChanges();
return { };
}
#if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT)
Protocol::ErrorStringOr<void> InspectorPageAgent::setForcedAppearance(std::optional<Protocol::Page::Appearance>&& appearance)
{
if (!appearance) {
m_inspectedPage.setUseDarkAppearanceOverride(std::nullopt);
return { };
}
switch (*appearance) {
case Protocol::Page::Appearance::Light:
m_inspectedPage.setUseDarkAppearanceOverride(false);
return { };
case Protocol::Page::Appearance::Dark:
m_inspectedPage.setUseDarkAppearanceOverride(true);
return { };
}
ASSERT_NOT_REACHED();
return { };
}
#endif
void InspectorPageAgent::applyUserAgentOverride(String& userAgent)
{
if (!m_userAgentOverride.isEmpty())
userAgent = m_userAgentOverride;
}
void InspectorPageAgent::applyEmulatedMedia(String& media)
{
if (!m_emulatedMedia.isEmpty())
media = m_emulatedMedia;
}
Protocol::ErrorStringOr<String> InspectorPageAgent::snapshotNode(Protocol::DOM::NodeId nodeId)
{
Protocol::ErrorString errorString;
InspectorDOMAgent* domAgent = m_instrumentingAgents.persistentDOMAgent();
ASSERT(domAgent);
Node* node = domAgent->assertNode(errorString, nodeId);
if (!node)
return makeUnexpected(errorString);
auto snapshot = WebCore::snapshotNode(m_inspectedPage.mainFrame(), *node, { { }, PixelFormat::BGRA8, DestinationColorSpace::SRGB() });
if (!snapshot)
return makeUnexpected("Could not capture snapshot"_s);
return snapshot->toDataURL("image/png"_s, std::nullopt, PreserveResolution::Yes);
}
Protocol::ErrorStringOr<String> InspectorPageAgent::snapshotRect(int x, int y, int width, int height, Protocol::Page::CoordinateSystem coordinateSystem)
{
SnapshotOptions options { { }, PixelFormat::BGRA8, DestinationColorSpace::SRGB() };
if (coordinateSystem == Protocol::Page::CoordinateSystem::Viewport)
options.flags.add(SnapshotFlags::InViewCoordinates);
IntRect rectangle(x, y, width, height);
auto snapshot = snapshotFrameRect(m_inspectedPage.mainFrame(), rectangle, WTFMove(options));
if (!snapshot)
return makeUnexpected("Could not capture snapshot"_s);
return snapshot->toDataURL("image/png"_s, std::nullopt, PreserveResolution::Yes);
}
#if ENABLE(WEB_ARCHIVE) && USE(CF)
Protocol::ErrorStringOr<String> InspectorPageAgent::archive()
{
auto archive = LegacyWebArchive::create(m_inspectedPage.mainFrame());
if (!archive)
return makeUnexpected("Could not create web archive for main frame"_s);
RetainPtr<CFDataRef> buffer = archive->rawDataRepresentation();
return base64EncodeToString(CFDataGetBytePtr(buffer.get()), CFDataGetLength(buffer.get()));
}
#endif
#if !PLATFORM(COCOA)
Protocol::ErrorStringOr<void> InspectorPageAgent::setScreenSizeOverride(std::optional<int>&& width, std::optional<int>&& height)
{
if (width.has_value() != height.has_value())
return makeUnexpected("Screen width and height override should be both specified or omitted"_s);
if (width && *width <= 0)
return makeUnexpected("Screen width override should be a positive integer"_s);
if (height && *height <= 0)
return makeUnexpected("Screen height override should be a positive integer"_s);
m_inspectedPage.mainFrame().setOverrideScreenSize(FloatSize(width.value_or(0), height.value_or(0)));
return { };
}
#endif
} // namespace WebCore