From ed39499cea979c72d28d145d996dd9f0d98cafa6 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 5 Dec 2019 14:01:25 -0800 Subject: [PATCH 01/42] rebaseline(webkit): rebaseline webkit atop of @131efe8ad (#154) Review URL: https://github.com/aslushnikov/webkit/commit/36a589c93d7828f6ce589b1a284aaba035c342dc --- browser_patches/webkit/BUILD_NUMBER | 2 +- browser_patches/webkit/UPSTREAM_CONFIG.sh | 2 +- .../webkit/patches/0001-chore-bootstrap.patch | 253 ++++++++++-------- 3 files changed, 141 insertions(+), 116 deletions(-) diff --git a/browser_patches/webkit/BUILD_NUMBER b/browser_patches/webkit/BUILD_NUMBER index 2d1420d537..9951021cfe 100644 --- a/browser_patches/webkit/BUILD_NUMBER +++ b/browser_patches/webkit/BUILD_NUMBER @@ -1 +1 @@ -1012 +1013 diff --git a/browser_patches/webkit/UPSTREAM_CONFIG.sh b/browser_patches/webkit/UPSTREAM_CONFIG.sh index 349da77075..72d8e53eff 100644 --- a/browser_patches/webkit/UPSTREAM_CONFIG.sh +++ b/browser_patches/webkit/UPSTREAM_CONFIG.sh @@ -1,3 +1,3 @@ REMOTE_URL="https://github.com/webkit/webkit" BASE_BRANCH="master" -BASE_REVISION="031545c904ac108f0063861f58a3e4e2a299b0c0" +BASE_REVISION="131efe8ad014ffa190946fea083b8f96b16f6e89" diff --git a/browser_patches/webkit/patches/0001-chore-bootstrap.patch b/browser_patches/webkit/patches/0001-chore-bootstrap.patch index 305e911d38..26f468f191 100644 --- a/browser_patches/webkit/patches/0001-chore-bootstrap.patch +++ b/browser_patches/webkit/patches/0001-chore-bootstrap.patch @@ -1,6 +1,6 @@ -From c71917697866e90900049e7b08979fdb23b63958 Mon Sep 17 00:00:00 2001 -From: Pavel Feldman -Date: Wed, 4 Dec 2019 23:24:50 -0800 +From b33decbe420d2874925adee8e4dd36bc9255072c Mon Sep 17 00:00:00 2001 +From: Andrey Lushnikov +Date: Thu, 5 Dec 2019 13:50:00 -0800 Subject: [PATCH] chore: bootstrap --- @@ -17,7 +17,7 @@ Subject: [PATCH] chore: bootstrap .../inspector/protocol/Emulation.json | 21 + .../inspector/protocol/Input.json | 160 ++++++ .../inspector/protocol/Page.json | 90 +++- - .../inspector/protocol/Target.json | 18 +- + .../inspector/protocol/Target.json | 20 +- Source/WebCore/html/FileInputType.cpp | 6 + .../inspector/InspectorInstrumentation.cpp | 14 +- .../inspector/InspectorInstrumentation.h | 21 + @@ -68,8 +68,8 @@ Subject: [PATCH] chore: bootstrap .../UIProcess/BrowserInspectorController.h | 47 ++ .../WebKit/UIProcess/BrowserInspectorPipe.cpp | 35 ++ .../WebKit/UIProcess/BrowserInspectorPipe.h | 16 + - .../UIProcess/BrowserInspectorTargetAgent.cpp | 83 +++ - .../UIProcess/BrowserInspectorTargetAgent.h | 35 ++ + .../UIProcess/BrowserInspectorTargetAgent.cpp | 93 ++++ + .../UIProcess/BrowserInspectorTargetAgent.h | 37 ++ .../PopUpSOAuthorizationSession.h | 4 + .../PopUpSOAuthorizationSession.mm | 1 + Source/WebKit/UIProcess/Cocoa/UIDelegate.h | 2 + @@ -84,6 +84,7 @@ Subject: [PATCH] chore: bootstrap .../WebKit/UIProcess/RemoteInspectorPipe.cpp | 132 +++++ Source/WebKit/UIProcess/RemoteInspectorPipe.h | 43 ++ .../AuthenticatorManager.cpp | 1 + + .../Mock/MockAuthenticatorManager.cpp | 4 +- .../UIProcess/WebPageInspectorController.cpp | 56 +- .../UIProcess/WebPageInspectorController.h | 8 + .../WebPageInspectorEmulationAgent.cpp | 47 ++ @@ -109,7 +110,7 @@ Subject: [PATCH] chore: bootstrap .../wpe/WebPageInspectorEmulationAgentWPE.cpp | 18 + .../wpe/WebPageInspectorInputAgentWPE.cpp | 76 +++ .../wpe/WebPageInspectorTargetProxyWPE.cpp | 18 + - .../WebKit/WebKit.xcodeproj/project.pbxproj | 59 +- + .../WebKit/WebKit.xcodeproj/project.pbxproj | 58 ++ .../WebPage/WebPageInspectorTarget.cpp | 7 + .../WebPage/WebPageInspectorTarget.h | 1 + Source/WebKit/WebProcess/WebProcess.cpp | 3 +- @@ -121,7 +122,7 @@ Subject: [PATCH] chore: bootstrap .../mac/WK2BrowserWindowController.h | 3 + .../mac/WK2BrowserWindowController.m | 37 +- Tools/MiniBrowser/wpe/main.cpp | 37 ++ - 117 files changed, 4663 insertions(+), 73 deletions(-) + 118 files changed, 4678 insertions(+), 75 deletions(-) create mode 100644 Source/JavaScriptCore/inspector/protocol/Browser.json create mode 100644 Source/JavaScriptCore/inspector/protocol/Dialog.json create mode 100644 Source/JavaScriptCore/inspector/protocol/Emulation.json @@ -166,10 +167,10 @@ Subject: [PATCH] chore: bootstrap create mode 100644 Source/WebKit/UIProcess/wpe/WebPageInspectorTargetProxyWPE.cpp diff --git a/Source/JavaScriptCore/CMakeLists.txt b/Source/JavaScriptCore/CMakeLists.txt -index 0f8c4194064..a28f84c44ba 100644 +index c9a82a7854f..6e2aff19e20 100644 --- a/Source/JavaScriptCore/CMakeLists.txt +++ b/Source/JavaScriptCore/CMakeLists.txt -@@ -1143,16 +1143,20 @@ set(JavaScriptCore_INSPECTOR_DOMAINS +@@ -1141,16 +1141,20 @@ set(JavaScriptCore_INSPECTOR_DOMAINS ${JAVASCRIPTCORE_DIR}/inspector/protocol/Animation.json ${JAVASCRIPTCORE_DIR}/inspector/protocol/ApplicationCache.json ${JAVASCRIPTCORE_DIR}/inspector/protocol/Audit.json @@ -191,10 +192,10 @@ index 0f8c4194064..a28f84c44ba 100644 ${JAVASCRIPTCORE_DIR}/inspector/protocol/LayerTree.json ${JAVASCRIPTCORE_DIR}/inspector/protocol/Network.json diff --git a/Source/JavaScriptCore/DerivedSources.make b/Source/JavaScriptCore/DerivedSources.make -index f59212ff01c..ca6ef5f8d07 100644 +index 3866e2bcb37..c3b0dab1cc4 100644 --- a/Source/JavaScriptCore/DerivedSources.make +++ b/Source/JavaScriptCore/DerivedSources.make -@@ -238,16 +238,20 @@ INSPECTOR_DOMAINS := \ +@@ -239,16 +239,20 @@ INSPECTOR_DOMAINS := \ $(JavaScriptCore)/inspector/protocol/Animation.json \ $(JavaScriptCore)/inspector/protocol/ApplicationCache.json \ $(JavaScriptCore)/inspector/protocol/Audit.json \ @@ -320,7 +321,7 @@ index 95d9d81188e..6f96f174dff 100644 // Note that 'unused' is a workaround so the compiler can pick the right sendResponse based on arity. // When is fixed or this class is renamed for the JSON::Object case, diff --git a/Source/JavaScriptCore/inspector/InspectorTarget.h b/Source/JavaScriptCore/inspector/InspectorTarget.h -index a9f04b8a0c9..045b9ecd39a 100644 +index 4b95964db4d..be7510ba897 100644 --- a/Source/JavaScriptCore/inspector/InspectorTarget.h +++ b/Source/JavaScriptCore/inspector/InspectorTarget.h @@ -45,6 +45,7 @@ public: @@ -330,18 +331,18 @@ index a9f04b8a0c9..045b9ecd39a 100644 + virtual String url() const = 0; virtual bool isProvisional() const { return false; } - -@@ -52,6 +53,8 @@ public: + bool isPaused() const { return m_isPaused; } +@@ -56,6 +57,8 @@ public: virtual void connect(FrontendChannel::ConnectionType) = 0; virtual void disconnect() = 0; virtual void sendMessageToTargetBackend(const String&) = 0; + virtual void activate(String& error) { error = "Target cannot be activated"; } + virtual void close(String& error) { error = "Target cannot be closed"; } - }; - } // namespace Inspector + private: + WTF::Function m_resumeCallback; diff --git a/Source/JavaScriptCore/inspector/agents/InspectorTargetAgent.cpp b/Source/JavaScriptCore/inspector/agents/InspectorTargetAgent.cpp -index 1177090fc18..764b62c727c 100644 +index 8fcb5a1e557..06ba4cb74ad 100644 --- a/Source/JavaScriptCore/inspector/agents/InspectorTargetAgent.cpp +++ b/Source/JavaScriptCore/inspector/agents/InspectorTargetAgent.cpp @@ -30,11 +30,12 @@ @@ -358,7 +359,7 @@ index 1177090fc18..764b62c727c 100644 { } -@@ -65,6 +66,28 @@ void InspectorTargetAgent::sendMessageToTarget(ErrorString& errorString, const S +@@ -87,6 +88,28 @@ void InspectorTargetAgent::sendMessageToTarget(ErrorString& errorString, const S target->sendMessageToTargetBackend(message); } @@ -387,7 +388,7 @@ index 1177090fc18..764b62c727c 100644 void InspectorTargetAgent::sendMessageFromTargetToFrontend(const String& targetId, const String& message) { ASSERT_WITH_MESSAGE(m_targets.get(targetId), "Sending a message from an untracked target to the frontend."); -@@ -87,14 +110,17 @@ static Protocol::Target::TargetInfo::Type targetTypeToProtocolType(InspectorTarg +@@ -109,16 +132,19 @@ static Protocol::Target::TargetInfo::Type targetTypeToProtocolType(InspectorTarg return Protocol::Target::TargetInfo::Type::Page; } @@ -401,13 +402,15 @@ index 1177090fc18..764b62c727c 100644 .release(); if (target.isProvisional()) result->setIsProvisional(true); + if (target.isPaused()) + result->setIsPaused(true); + if (!browserContextID.isEmpty()) + result->setBrowserContextId(browserContextID); return result; } -@@ -108,7 +134,7 @@ void InspectorTargetAgent::targetCreated(InspectorTarget& target) - +@@ -134,7 +160,7 @@ void InspectorTargetAgent::targetCreated(InspectorTarget& target) + target.pause(); target.connect(connectionType()); - m_frontendDispatcher->targetCreated(buildTargetInfoObject(target)); @@ -415,7 +418,7 @@ index 1177090fc18..764b62c727c 100644 } void InspectorTargetAgent::targetDestroyed(InspectorTarget& target) -@@ -135,6 +161,18 @@ void InspectorTargetAgent::didCommitProvisionalTarget(const String& oldTargetID, +@@ -159,6 +185,18 @@ void InspectorTargetAgent::didCommitProvisionalTarget(const String& oldTargetID, m_frontendDispatcher->didCommitProvisionalTarget(oldTargetID, committedTargetID); } @@ -434,7 +437,7 @@ index 1177090fc18..764b62c727c 100644 FrontendChannel::ConnectionType InspectorTargetAgent::connectionType() const { return m_router.hasLocalFrontend() ? Inspector::FrontendChannel::ConnectionType::Local : Inspector::FrontendChannel::ConnectionType::Remote; -@@ -144,7 +182,7 @@ void InspectorTargetAgent::connectToTargets() +@@ -168,7 +206,7 @@ void InspectorTargetAgent::connectToTargets() { for (InspectorTarget* target : m_targets.values()) { target->connect(connectionType()); @@ -444,7 +447,7 @@ index 1177090fc18..764b62c727c 100644 } diff --git a/Source/JavaScriptCore/inspector/agents/InspectorTargetAgent.h b/Source/JavaScriptCore/inspector/agents/InspectorTargetAgent.h -index 38cb318986b..4287e05e559 100644 +index 1eb7abb2fa2..5a71d29af64 100644 --- a/Source/JavaScriptCore/inspector/agents/InspectorTargetAgent.h +++ b/Source/JavaScriptCore/inspector/agents/InspectorTargetAgent.h @@ -41,7 +41,7 @@ class JS_EXPORT_PRIVATE InspectorTargetAgent : public InspectorAgentBase, public @@ -456,9 +459,9 @@ index 38cb318986b..4287e05e559 100644 ~InspectorTargetAgent() override; // InspectorAgentBase -@@ -50,11 +50,14 @@ public: - - // TargetBackendDispatcherHandler +@@ -52,11 +52,14 @@ public: + void setPauseOnStart(ErrorString&, bool pauseOnStart) override; + void resume(ErrorString&, const String& targetId) override; void sendMessageToTarget(ErrorString&, const String& targetId, const String& message) final; + void activate(ErrorString&, const String& targetId) override; + void close(ErrorString&, const String& targetId) override; @@ -471,14 +474,14 @@ index 38cb318986b..4287e05e559 100644 // Target messages. void sendMessageFromTargetToFrontend(const String& targetId, const String& message); -@@ -68,6 +71,7 @@ private: +@@ -70,6 +73,7 @@ private: Inspector::FrontendRouter& m_router; std::unique_ptr m_frontendDispatcher; Ref m_backendDispatcher; + const String m_browserContextID; HashMap m_targets; bool m_isConnected { false }; - }; + bool m_shouldPauseOnStart { false }; diff --git a/Source/JavaScriptCore/inspector/protocol/Browser.json b/Source/JavaScriptCore/inspector/protocol/Browser.json new file mode 100644 index 00000000000..063e5e1346a @@ -1016,21 +1019,23 @@ index 367d1f235a8..b2ed9177528 100644 ] } diff --git a/Source/JavaScriptCore/inspector/protocol/Target.json b/Source/JavaScriptCore/inspector/protocol/Target.json -index 240cd42e67e..f635c67ef3f 100644 +index 52920cded24..77a4ffb9711 100644 --- a/Source/JavaScriptCore/inspector/protocol/Target.json +++ b/Source/JavaScriptCore/inspector/protocol/Target.json -@@ -10,7 +10,9 @@ +@@ -10,8 +10,10 @@ "properties": [ { "name": "targetId", "type": "string", "description": "Unique identifier for the target." }, { "name": "type", "type": "string", "enum": ["page", "service-worker", "worker"] }, -- { "name": "isProvisional", "type": "boolean", "optional": true, "description": "True value indicates that this is a provisional page target i.e. Such target may be created when current page starts cross-origin navigation. Eventually each provisional target is either committed and swaps with the current target or gets destroyed, e.g. in case of load request failure." } -+ { "name": "url", "type": "string" }, +- { "name": "isProvisional", "type": "boolean", "optional": true, "description": "Whether this is a provisional page target." }, +- { "name": "isPaused", "type": "boolean", "optional": true, "description": "Whether the target is paused on start and has to be explicitely resumed by inspector." } + { "name": "isProvisional", "type": "boolean", "optional": true, "description": "True value indicates that this is a provisional page target i.e. Such target may be created when current page starts cross-origin navigation. Eventually each provisional target is either committed and swaps with the current target or gets destroyed, e.g. in case of load request failure." }, ++ { "name": "isPaused", "type": "boolean", "optional": true, "description": "Whether the target is paused on start and has to be explicitely resumed by inspector." }, ++ { "name": "url", "type": "string" }, + { "name": "browserContextId", "$ref": "Browser.ContextID", "optional": true } ] } ], -@@ -22,6 +24,20 @@ +@@ -37,6 +39,20 @@ { "name": "targetId", "type": "string" }, { "name": "message", "type": "string", "description": "JSON Inspector Protocol message (command) to be dispatched on the backend." } ] @@ -2047,7 +2052,7 @@ index 8c4a104da04..3dc08926a75 100644 } } diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp -index 9b4211b4212..5881bd624df 100644 +index e105be0ba92..2557eacc4b7 100644 --- a/Source/WebCore/loader/FrameLoader.cpp +++ b/Source/WebCore/loader/FrameLoader.cpp @@ -1179,6 +1179,7 @@ void FrameLoader::loadInSameDocument(const URL& url, SerializedScriptValue* stat @@ -2071,10 +2076,10 @@ index 9c58b06f4c4..3d624733c36 100644 if (stateObjectType == StateObjectType::Push) { frame->loader().history().pushState(WTFMove(data), title, fullURL.string()); diff --git a/Source/WebCore/platform/PlatformKeyboardEvent.h b/Source/WebCore/platform/PlatformKeyboardEvent.h -index 4aaf5bde32b..e9c047d4f26 100644 +index 16b3719f77d..d96fd15db01 100644 --- a/Source/WebCore/platform/PlatformKeyboardEvent.h +++ b/Source/WebCore/platform/PlatformKeyboardEvent.h -@@ -147,6 +147,7 @@ namespace WebCore { +@@ -132,6 +132,7 @@ namespace WebCore { static String keyCodeForHardwareKeyCode(unsigned); static String keyIdentifierForGdkKeyCode(unsigned); static int windowsKeyCodeForGdkKeyCode(unsigned); @@ -2082,7 +2087,7 @@ index 4aaf5bde32b..e9c047d4f26 100644 static String singleCharacterString(unsigned); static bool modifiersContainCapsLock(unsigned); #endif -@@ -156,6 +157,7 @@ namespace WebCore { +@@ -141,6 +142,7 @@ namespace WebCore { static String keyCodeForHardwareKeyCode(unsigned); static String keyIdentifierForWPEKeyCode(unsigned); static int windowsKeyCodeForWPEKeyCode(unsigned); @@ -2624,7 +2629,7 @@ index dfc490d2a0b..0bcd1d98ad8 100644 Vector NetworkStorageSession::getCookies(const URL& url) diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.cpp b/Source/WebKit/NetworkProcess/NetworkProcess.cpp -index 5f23cc66aa1..488c3c3bbae 100644 +index 932357e966a..a5858700a7a 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.cpp +++ b/Source/WebKit/NetworkProcess/NetworkProcess.cpp @@ -26,7 +26,6 @@ @@ -2635,8 +2640,8 @@ index 5f23cc66aa1..488c3c3bbae 100644 #include "ArgumentCoders.h" #include "Attachment.h" #include "AuthenticationManager.h" -@@ -580,6 +579,35 @@ void NetworkProcess::destroySession(PAL::SessionID sessionID) - m_storageQuotaManagers.remove(sessionID); +@@ -561,6 +560,35 @@ void NetworkProcess::destroySession(PAL::SessionID sessionID) + m_storageManagerSet->remove(sessionID); } +void NetworkProcess::getAllCookies(PAL::SessionID sessionID, CompletionHandler&&)>&& completionHandler) @@ -2672,7 +2677,7 @@ index 5f23cc66aa1..488c3c3bbae 100644 void NetworkProcess::dumpResourceLoadStatistics(PAL::SessionID sessionID, CompletionHandler&& completionHandler) { diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.h b/Source/WebKit/NetworkProcess/NetworkProcess.h -index f62f01d380f..e33439eb278 100644 +index 4d4add00ce3..c27f9ffe6be 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.h +++ b/Source/WebKit/NetworkProcess/NetworkProcess.h @@ -74,6 +74,7 @@ class SessionID; @@ -2695,10 +2700,10 @@ index f62f01d380f..e33439eb278 100644 void clearPrevalentResource(PAL::SessionID, const RegistrableDomain&, CompletionHandler&&); void clearUserInteraction(PAL::SessionID, const RegistrableDomain&, CompletionHandler&&); diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in -index 3677677480b..2873e1c0f59 100644 +index 0257d8d23ef..0d573802a70 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in +++ b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in -@@ -79,6 +79,10 @@ messages -> NetworkProcess LegacyReceiver { +@@ -80,6 +80,10 @@ messages -> NetworkProcess LegacyReceiver { PrepareToSuspend(bool isSuspensionImminent) -> () Async ProcessDidResume() @@ -2722,7 +2727,7 @@ index 898e30b370d..74945e06fac 100644 #include #include diff --git a/Source/WebKit/Shared/NativeWebKeyboardEvent.h b/Source/WebKit/Shared/NativeWebKeyboardEvent.h -index 6f4e29b7c65..9dd287efc40 100644 +index 05938ef3564..b050b30281c 100644 --- a/Source/WebKit/Shared/NativeWebKeyboardEvent.h +++ b/Source/WebKit/Shared/NativeWebKeyboardEvent.h @@ -34,6 +34,7 @@ @@ -2760,7 +2765,7 @@ index 0fa557e9faa..db299d91de3 100644 NativeWebMouseEvent(NSEvent *, NSEvent *lastPressureEvent, NSView *); #elif PLATFORM(GTK) diff --git a/Source/WebKit/Shared/WebEvent.h b/Source/WebKit/Shared/WebEvent.h -index c36100cf5c4..216402f0a24 100644 +index f77a16bef13..73f99282f08 100644 --- a/Source/WebKit/Shared/WebEvent.h +++ b/Source/WebKit/Shared/WebEvent.h @@ -35,6 +35,7 @@ @@ -2788,7 +2793,7 @@ index c36100cf5c4..216402f0a24 100644 #elif PLATFORM(IOS_FAMILY) WebKeyboardEvent(Type, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool handledByInputMethod, bool isAutoRepeat, bool isKeypad, bool isSystemKey, OptionSet, WallTime timestamp); #elif USE(LIBWPE) -@@ -309,7 +311,7 @@ private: +@@ -301,7 +303,7 @@ private: int32_t m_nativeVirtualKeyCode; int32_t m_macCharCode; #if USE(APPKIT) || USE(UIKIT_KEYBOARD_ADDITIONS) || PLATFORM(GTK) @@ -2798,10 +2803,10 @@ index c36100cf5c4..216402f0a24 100644 #if USE(APPKIT) Vector m_commands; diff --git a/Source/WebKit/Shared/WebKeyboardEvent.cpp b/Source/WebKit/Shared/WebKeyboardEvent.cpp -index a5a23cf148e..390eaf847b6 100644 +index b5955a8b797..3d0c7c8e40f 100644 --- a/Source/WebKit/Shared/WebKeyboardEvent.cpp +++ b/Source/WebKit/Shared/WebKeyboardEvent.cpp -@@ -81,6 +81,28 @@ WebKeyboardEvent::WebKeyboardEvent(Type type, const String& text, const String& +@@ -77,6 +77,28 @@ WebKeyboardEvent::WebKeyboardEvent(Type type, const String& text, const String& ASSERT(isKeyboardEventType(type)); } @@ -2859,10 +2864,10 @@ index 58e37fe3827..429d245ea99 100644 } diff --git a/Source/WebKit/Sources.txt b/Source/WebKit/Sources.txt -index 3f7d3fb6216..f396e22edf5 100644 +index 73ff4d27bee..4f92dfdfe50 100644 --- a/Source/WebKit/Sources.txt +++ b/Source/WebKit/Sources.txt -@@ -241,17 +241,23 @@ Shared/WebsiteData/WebsiteData.cpp +@@ -246,17 +246,23 @@ Shared/WebsiteData/WebsiteData.cpp UIProcess/AuxiliaryProcessProxy.cpp UIProcess/BackgroundProcessResponsivenessTimer.cpp @@ -2886,7 +2891,7 @@ index 3f7d3fb6216..f396e22edf5 100644 UIProcess/RemoteWebInspectorProxy.cpp UIProcess/ResponsivenessTimer.cpp UIProcess/StatisticsRequest.cpp -@@ -293,6 +299,9 @@ UIProcess/WebPageDiagnosticLoggingClient.cpp +@@ -298,6 +304,9 @@ UIProcess/WebPageDiagnosticLoggingClient.cpp UIProcess/WebPageGroup.cpp UIProcess/WebPageInjectedBundleClient.cpp UIProcess/WebPageInspectorController.cpp @@ -2897,10 +2902,10 @@ index 3f7d3fb6216..f396e22edf5 100644 UIProcess/WebPasteboardProxy.cpp UIProcess/WebPreferences.cpp diff --git a/Source/WebKit/SourcesCocoa.txt b/Source/WebKit/SourcesCocoa.txt -index 63c4dfa10ab..c9a7dcf3dea 100644 +index 9d242e2a064..819e404bc23 100644 --- a/Source/WebKit/SourcesCocoa.txt +++ b/Source/WebKit/SourcesCocoa.txt -@@ -243,6 +243,7 @@ UIProcess/API/Cocoa/_WKApplicationManifest.mm +@@ -247,6 +247,7 @@ UIProcess/API/Cocoa/_WKApplicationManifest.mm UIProcess/API/Cocoa/_WKAttachment.mm UIProcess/API/Cocoa/_WKAutomationSession.mm UIProcess/API/Cocoa/_WKAutomationSessionConfiguration.mm @@ -2982,10 +2987,10 @@ index 54513035b26..2d3200e4f6e 100644 virtual void setStatusText(WebKit::WebPageProxy*, const WTF::String&) { } virtual void mouseDidMoveOverElement(WebKit::WebPageProxy&, const WebKit::WebHitTestResultData&, OptionSet, Object*) { } diff --git a/Source/WebKit/UIProcess/API/C/WKPage.cpp b/Source/WebKit/UIProcess/API/C/WKPage.cpp -index 19e273187ae..14b3ef74afb 100644 +index 4d8f6cf2e46..3373ec7aa1b 100644 --- a/Source/WebKit/UIProcess/API/C/WKPage.cpp +++ b/Source/WebKit/UIProcess/API/C/WKPage.cpp -@@ -1674,6 +1674,8 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient +@@ -1675,6 +1675,8 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient completionHandler(String()); } @@ -2994,7 +2999,7 @@ index 19e273187ae..14b3ef74afb 100644 void setStatusText(WebPageProxy* page, const String& text) final { if (!m_client.setStatusText) -@@ -1734,6 +1736,8 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient +@@ -1735,6 +1737,8 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient { if (!m_client.didNotHandleKeyEvent) return; @@ -3275,10 +3280,10 @@ index 00000000000..ab6b7621d10 + +WebKit::WebPageProxy* webkitBrowserInspectorCreateNewPageInContext(WebKitWebContext*); diff --git a/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp b/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp -index 47801342ea6..ef163b6615a 100644 +index f769407fdc6..ba010ed593b 100644 --- a/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp +++ b/Source/WebKit/UIProcess/API/glib/WebKitUIClient.cpp -@@ -90,6 +90,10 @@ private: +@@ -91,6 +91,10 @@ private: { webkitWebViewRunJavaScriptPrompt(m_webView, message.utf8(), defaultValue.utf8(), WTFMove(completionHandler)); } @@ -3290,10 +3295,10 @@ index 47801342ea6..ef163b6615a 100644 bool canRunBeforeUnloadConfirmPanel() const final { return true; } diff --git a/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp b/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp -index 126bccf1314..a095db63bc5 100644 +index 33a9b7d5ad0..406c6431bd8 100644 --- a/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp +++ b/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp -@@ -373,6 +373,11 @@ static void webkitWebContextConstructed(GObject* object) +@@ -385,6 +385,11 @@ static void webkitWebContextConstructed(GObject* object) if (!webkit_website_data_manager_is_ephemeral(priv->websiteDataManager.get())) WebKit::LegacyGlobalSettings::singleton().setHSTSStorageDirectory(FileSystem::stringFromFileSystemRepresentation(webkit_website_data_manager_get_hsts_cache_directory(priv->websiteDataManager.get()))); @@ -3722,10 +3727,10 @@ index 00000000000..ac0caaabaed +#endif // ENABLE(REMOTE_INSPECTOR) diff --git a/Source/WebKit/UIProcess/BrowserInspectorTargetAgent.cpp b/Source/WebKit/UIProcess/BrowserInspectorTargetAgent.cpp new file mode 100644 -index 00000000000..a2873d5e0f7 +index 00000000000..885d7e02cbe --- /dev/null +++ b/Source/WebKit/UIProcess/BrowserInspectorTargetAgent.cpp -@@ -0,0 +1,83 @@ +@@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + @@ -3778,6 +3783,16 @@ index 00000000000..a2873d5e0f7 +{ +} + ++void BrowserInspectorTargetAgent::setPauseOnStart(ErrorString& error, bool pauseOnStart) ++{ ++ error = "'setPauseOnStart' is not implemented for browser target"; ++} ++ ++void BrowserInspectorTargetAgent::resume(ErrorString& error, const String& in_targetId) ++{ ++ error = "'resume' is not implemented for browser target"; ++} ++ +void BrowserInspectorTargetAgent::sendMessageToTarget(ErrorString& error, const String& in_targetId, const String& in_message) +{ + auto* target = targetForId(in_targetId); @@ -3811,10 +3826,10 @@ index 00000000000..a2873d5e0f7 +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/BrowserInspectorTargetAgent.h b/Source/WebKit/UIProcess/BrowserInspectorTargetAgent.h new file mode 100644 -index 00000000000..5c274280846 +index 00000000000..b5819aaba56 --- /dev/null +++ b/Source/WebKit/UIProcess/BrowserInspectorTargetAgent.h -@@ -0,0 +1,35 @@ +@@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + @@ -3844,6 +3859,8 @@ index 00000000000..5c274280846 + void sendMessageToTarget(Inspector::ErrorString&, const String& targetId, const String& message) final; + void activate(Inspector::ErrorString&, const String& targetId) override; + void close(Inspector::ErrorString&, const String& targetId) override; ++ void setPauseOnStart(Inspector::ErrorString&, bool pauseOnStart) override; ++ void resume(Inspector::ErrorString&, const String& in_targetId) override; + +private: + Ref m_backendDispatcher; @@ -4426,7 +4443,7 @@ index 00000000000..203c203a0e2 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/InspectorTargetProxy.cpp b/Source/WebKit/UIProcess/InspectorTargetProxy.cpp -index 1b37c1ed439..c45d45de342 100644 +index 55c8173ab24..ff2e987171f 100644 --- a/Source/WebKit/UIProcess/InspectorTargetProxy.cpp +++ b/Source/WebKit/UIProcess/InspectorTargetProxy.cpp @@ -32,6 +32,8 @@ @@ -4713,8 +4730,24 @@ index baabe8def1d..83d089d87d0 100644 #include #include #include +diff --git a/Source/WebKit/UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp b/Source/WebKit/UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp +index 2c4f9ddabf0..ae9e0b80708 100644 +--- a/Source/WebKit/UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp ++++ b/Source/WebKit/UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp +@@ -53,9 +53,9 @@ void MockAuthenticatorManager::respondReceivedInternal(Respond&& respond) + void MockAuthenticatorManager::filterTransports(TransportSet& transports) const + { + if (!m_testConfiguration.nfc) +- transports.remove(AuthenticatorTransport::Nfc); ++ transports.remove(WebCore::AuthenticatorTransport::Nfc); + if (!m_testConfiguration.local) +- transports.remove(AuthenticatorTransport::Internal); ++ transports.remove(WebCore::AuthenticatorTransport::Internal); + } + + } // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageInspectorController.cpp b/Source/WebKit/UIProcess/WebPageInspectorController.cpp -index b9a9469ab59..81129896554 100644 +index 1ee28bf7163..e4ae2ad50b5 100644 --- a/Source/WebKit/UIProcess/WebPageInspectorController.cpp +++ b/Source/WebKit/UIProcess/WebPageInspectorController.cpp @@ -26,9 +26,11 @@ @@ -4821,7 +4854,7 @@ index b9a9469ab59..81129896554 100644 } void WebPageInspectorController::destroyInspectorTarget(const String& targetId) -@@ -169,7 +219,7 @@ void WebPageInspectorController::sendMessageToInspectorFrontend(const String& ta +@@ -186,7 +236,7 @@ void WebPageInspectorController::setContinueLoadingCallback(const ProvisionalPag void WebPageInspectorController::didCreateProvisionalPage(ProvisionalPageProxy& provisionalPage) { @@ -4831,7 +4864,7 @@ index b9a9469ab59..81129896554 100644 void WebPageInspectorController::willDestroyProvisionalPage(const ProvisionalPageProxy& provisionalPage) diff --git a/Source/WebKit/UIProcess/WebPageInspectorController.h b/Source/WebKit/UIProcess/WebPageInspectorController.h -index 828bc3ccc7e..40a333b7004 100644 +index 78caedf0c0c..40f08285590 100644 --- a/Source/WebKit/UIProcess/WebPageInspectorController.h +++ b/Source/WebKit/UIProcess/WebPageInspectorController.h @@ -48,7 +48,13 @@ public: @@ -5426,10 +5459,10 @@ index 00000000000..0655b5ea376 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp -index 81b65c6e2db..2b7677a25a2 100644 +index 35cd3ac33fc..05f3cc29314 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp -@@ -867,6 +867,7 @@ void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason) +@@ -868,6 +868,7 @@ void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason) m_pageLoadState.didSwapWebProcesses(); if (reason != ProcessLaunchReason::InitialProcess) m_drawingArea->waitForBackingStoreUpdateOnNextPaint(); @@ -5437,7 +5470,7 @@ index 81b65c6e2db..2b7677a25a2 100644 } void WebPageProxy::didAttachToRunningProcess() -@@ -1620,6 +1621,11 @@ void WebPageProxy::setControlledByAutomation(bool controlled) +@@ -1623,6 +1624,11 @@ void WebPageProxy::setControlledByAutomation(bool controlled) m_process->processPool().sendToNetworkingProcess(Messages::NetworkProcess::SetSessionIsControlledByAutomation(m_websiteDataStore->sessionID(), m_controlledByAutomation)); } @@ -5449,7 +5482,7 @@ index 81b65c6e2db..2b7677a25a2 100644 void WebPageProxy::createInspectorTarget(const String& targetId, Inspector::InspectorTargetType type) { m_inspectorController->createInspectorTarget(targetId, type); -@@ -5330,6 +5336,8 @@ void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, SecurityOriginDat +@@ -5339,6 +5345,8 @@ void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, SecurityOriginDat if (auto* automationSession = process().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -5458,7 +5491,7 @@ index 81b65c6e2db..2b7677a25a2 100644 m_uiClient->runJavaScriptAlert(*this, message, frame, WTFMove(securityOrigin), WTFMove(reply)); } -@@ -5349,6 +5357,8 @@ void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, SecurityOriginD +@@ -5358,6 +5366,8 @@ void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, SecurityOriginD if (auto* automationSession = process().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -5467,7 +5500,7 @@ index 81b65c6e2db..2b7677a25a2 100644 m_uiClient->runJavaScriptConfirm(*this, message, frame, WTFMove(securityOrigin), WTFMove(reply)); } -@@ -5368,6 +5378,8 @@ void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, SecurityOriginDa +@@ -5377,6 +5387,8 @@ void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, SecurityOriginDa if (auto* automationSession = process().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -5476,7 +5509,7 @@ index 81b65c6e2db..2b7677a25a2 100644 m_uiClient->runJavaScriptPrompt(*this, message, defaultValue, frame, WTFMove(securityOrigin), WTFMove(reply)); } -@@ -5526,6 +5538,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, Security +@@ -5536,6 +5548,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, Security return; } } @@ -5485,7 +5518,7 @@ index 81b65c6e2db..2b7677a25a2 100644 // Since runBeforeUnloadConfirmPanel() can spin a nested run loop we need to turn off the responsiveness timer. m_process->responsivenessTimer().stop(); -@@ -6543,6 +6557,8 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) +@@ -6558,6 +6572,8 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) if (auto* automationSession = process().processPool().automationSession()) automationSession->mouseEventsFlushedForPage(*this); pageClient().didFinishProcessingAllPendingMouseEvents(); @@ -5494,7 +5527,7 @@ index 81b65c6e2db..2b7677a25a2 100644 } break; -@@ -6569,7 +6585,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) +@@ -6584,7 +6600,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) case WebEvent::RawKeyDown: case WebEvent::Char: { LOG(KeyHandling, "WebPageProxy::didReceiveEvent: %s (queue empty %d)", webKeyboardEventTypeString(type), m_keyEventQueue.isEmpty()); @@ -5502,7 +5535,7 @@ index 81b65c6e2db..2b7677a25a2 100644 MESSAGE_CHECK(m_process, !m_keyEventQueue.isEmpty()); NativeWebKeyboardEvent event = m_keyEventQueue.takeFirst(); -@@ -6584,7 +6599,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) +@@ -6604,7 +6619,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) // The call to doneWithKeyEvent may close this WebPage. // Protect against this being destroyed. Ref protect(*this); @@ -5510,7 +5543,7 @@ index 81b65c6e2db..2b7677a25a2 100644 pageClient().doneWithKeyEvent(event, handled); if (!handled) m_uiClient->didNotHandleKeyEvent(this, event); -@@ -6593,6 +6607,8 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) +@@ -6613,6 +6627,8 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) if (!canProcessMoreKeyEvents) { if (auto* automationSession = process().processPool().automationSession()) automationSession->keyboardEventsFlushedForPage(*this); @@ -5520,7 +5553,7 @@ index 81b65c6e2db..2b7677a25a2 100644 break; } diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h -index 488ac80306d..700b332f427 100644 +index 10947ff0dbb..c1a42229051 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h @@ -35,6 +35,7 @@ @@ -5555,7 +5588,7 @@ index 488ac80306d..700b332f427 100644 void initializeWebPage(); void setDrawingArea(std::unique_ptr&&); -@@ -2229,6 +2240,7 @@ private: +@@ -2234,6 +2245,7 @@ private: bool m_treatsSHA1CertificatesAsInsecure { true }; RefPtr m_inspector; @@ -5563,7 +5596,7 @@ index 488ac80306d..700b332f427 100644 #if ENABLE(FULLSCREEN_API) std::unique_ptr m_fullScreenManager; -@@ -2577,6 +2589,7 @@ private: +@@ -2582,6 +2594,7 @@ private: #if ENABLE(REMOTE_INSPECTOR) std::unique_ptr m_inspectorDebuggable; #endif @@ -6215,10 +6248,10 @@ index 00000000000..74dace1cc7c + +} // namespace WebKit diff --git a/Source/WebKit/WebKit.xcodeproj/project.pbxproj b/Source/WebKit/WebKit.xcodeproj/project.pbxproj -index 5cb81ccc202..2bf6772abb6 100644 +index 95ce08d8379..28bed022575 100644 --- a/Source/WebKit/WebKit.xcodeproj/project.pbxproj +++ b/Source/WebKit/WebKit.xcodeproj/project.pbxproj -@@ -1667,6 +1667,20 @@ +@@ -1673,6 +1673,20 @@ CEE4AE2B1A5DCF430002F49B /* UIKitSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = CEE4AE2A1A5DCF430002F49B /* UIKitSPI.h */; }; D3B9484711FF4B6500032B39 /* WebPopupMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = D3B9484311FF4B6500032B39 /* WebPopupMenu.h */; }; D3B9484911FF4B6500032B39 /* WebSearchPopupMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = D3B9484511FF4B6500032B39 /* WebSearchPopupMenu.h */; }; @@ -6239,7 +6272,7 @@ index 5cb81ccc202..2bf6772abb6 100644 E105FE5418D7B9DE008F57A8 /* EditingRange.h in Headers */ = {isa = PBXBuildFile; fileRef = E105FE5318D7B9DE008F57A8 /* EditingRange.h */; }; E11D35AE16B63D1B006D23D7 /* com.apple.WebProcess.sb in Resources */ = {isa = PBXBuildFile; fileRef = E1967E37150AB5E200C73169 /* com.apple.WebProcess.sb */; }; E14A954A16E016A40068DE82 /* NetworkProcessPlatformStrategies.h in Headers */ = {isa = PBXBuildFile; fileRef = E14A954816E016A40068DE82 /* NetworkProcessPlatformStrategies.h */; }; -@@ -4704,6 +4718,21 @@ +@@ -4761,6 +4775,21 @@ D3B9484311FF4B6500032B39 /* WebPopupMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebPopupMenu.h; sourceTree = ""; }; D3B9484411FF4B6500032B39 /* WebSearchPopupMenu.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebSearchPopupMenu.cpp; sourceTree = ""; }; D3B9484511FF4B6500032B39 /* WebSearchPopupMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebSearchPopupMenu.h; sourceTree = ""; }; @@ -6261,7 +6294,7 @@ index 5cb81ccc202..2bf6772abb6 100644 DF58C6311371AC5800F9A37C /* NativeWebWheelEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NativeWebWheelEvent.h; sourceTree = ""; }; DF58C6351371ACA000F9A37C /* NativeWebWheelEventMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NativeWebWheelEventMac.mm; sourceTree = ""; }; E105FE5318D7B9DE008F57A8 /* EditingRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditingRange.h; sourceTree = ""; }; -@@ -6305,6 +6334,7 @@ +@@ -6478,6 +6507,7 @@ 37C4C08318149C2A003688B9 /* Cocoa */ = { isa = PBXGroup; children = ( @@ -6269,7 +6302,7 @@ index 5cb81ccc202..2bf6772abb6 100644 1A43E826188F38E2009E4D30 /* Deprecated */, 37A5E01218BBF937000A081E /* _WKActivatedElementInfo.h */, 37A5E01118BBF937000A081E /* _WKActivatedElementInfo.mm */, -@@ -7783,6 +7813,14 @@ +@@ -7960,6 +7990,14 @@ BC032DC310F438260058C15A /* UIProcess */ = { isa = PBXGroup; children = ( @@ -6284,7 +6317,7 @@ index 5cb81ccc202..2bf6772abb6 100644 BC032DC410F4387C0058C15A /* API */, 512F588D12A8836F00629530 /* Authentication */, 9955A6E81C79809000EB6A93 /* Automation */, -@@ -8060,6 +8098,7 @@ +@@ -8238,6 +8276,7 @@ BC0C376610F807660076D7CB /* C */ = { isa = PBXGroup; children = ( @@ -6292,7 +6325,7 @@ index 5cb81ccc202..2bf6772abb6 100644 5123CF18133D25E60056F800 /* cg */, 6EE849C41368D9040038D481 /* mac */, BCB63477116BF10600603215 /* WebKit2_C.h */, -@@ -8655,6 +8694,11 @@ +@@ -8833,6 +8872,11 @@ BCCF085C113F3B7500C650C5 /* mac */ = { isa = PBXGroup; children = ( @@ -6304,7 +6337,7 @@ index 5cb81ccc202..2bf6772abb6 100644 B878B613133428DC006888E9 /* CorrectionPanel.h */, B878B614133428DC006888E9 /* CorrectionPanel.mm */, C1817362205844A900DFDA65 /* DisplayLink.cpp */, -@@ -9334,6 +9378,7 @@ +@@ -9513,6 +9557,7 @@ 510F59101DDE296900412FF5 /* _WKIconLoadingDelegate.h in Headers */, 37A64E5518F38E3C00EB30F1 /* _WKInputDelegate.h in Headers */, 5CAFDE452130846300B1F7E1 /* _WKInspector.h in Headers */, @@ -6312,7 +6345,7 @@ index 5cb81ccc202..2bf6772abb6 100644 5CAFDE472130846A00B1F7E1 /* _WKInspectorInternal.h in Headers */, 9979CA58237F49F10039EC05 /* _WKInspectorPrivate.h in Headers */, A5C0F0AB2000658200536536 /* _WKInspectorWindow.h in Headers */, -@@ -9447,6 +9492,7 @@ +@@ -9626,6 +9671,7 @@ 7C89D2981A6753B2003A5FDE /* APIPageConfiguration.h in Headers */, 1AC1336C18565C7A00F3EC05 /* APIPageHandle.h in Headers */, 1AFDD3151891B54000153970 /* APIPolicyClient.h in Headers */, @@ -6320,7 +6353,7 @@ index 5cb81ccc202..2bf6772abb6 100644 7CE4D2201A4914CA00C7F152 /* APIProcessPoolConfiguration.h in Headers */, F634445612A885C8000612D8 /* APISecurityOrigin.h in Headers */, 1AFDE6621954E9B100C48FFA /* APISessionState.h in Headers */, -@@ -9566,6 +9612,7 @@ +@@ -9745,6 +9791,7 @@ BC06F43A12DBCCFB002D78DE /* GeolocationPermissionRequestProxy.h in Headers */, 2DA944A41884E4F000ED86DB /* GestureTypes.h in Headers */, 2DA049B8180CCD0A00AAFA9E /* GraphicsLayerCARemote.h in Headers */, @@ -6328,7 +6361,7 @@ index 5cb81ccc202..2bf6772abb6 100644 C0CE72AD1247E78D00BC0EC4 /* HandleMessage.h in Headers */, 1AC75A1B1B3368270056745B /* HangDetectionDisabler.h in Headers */, 57AC8F50217FEED90055438C /* HidConnection.h in Headers */, -@@ -9689,8 +9736,10 @@ +@@ -9868,8 +9915,10 @@ 41DC45961E3D6E2200B11F51 /* NetworkRTCProvider.h in Headers */, 413075AB1DE85F330039EC69 /* NetworkRTCSocket.h in Headers */, 5C20CBA01BB1ECD800895BB1 /* NetworkSession.h in Headers */, @@ -6339,7 +6372,7 @@ index 5cb81ccc202..2bf6772abb6 100644 570DAAC22303730300E8FC04 /* NfcConnection.h in Headers */, 570DAAAE23026F5C00E8FC04 /* NfcService.h in Headers */, 31A2EC5614899C0900810D71 /* NotificationPermissionRequest.h in Headers */, -@@ -9772,6 +9821,7 @@ +@@ -9951,6 +10000,7 @@ CD2865EE2255562000606AC7 /* ProcessTaskStateObserver.h in Headers */, 463FD4821EB94EC000A2982C /* ProcessTerminationReason.h in Headers */, 86E67A251910B9D100004AB7 /* ProcessThrottler.h in Headers */, @@ -6347,15 +6380,7 @@ index 5cb81ccc202..2bf6772abb6 100644 83048AE61ACA45DC0082C832 /* ProcessThrottlerClient.h in Headers */, A1E688701F6E2BAB007006A6 /* QuarantineSPI.h in Headers */, 57FD318222B3515E008D0E8B /* RedirectSOAuthorizationSession.h in Headers */, -@@ -9820,7 +9870,6 @@ - 511F8A7B138B460900A95F44 /* SecItemShimLibrary.h in Headers */, - E18E690C169B563F009B6670 /* SecItemShimProxy.h in Headers */, - E18E6918169B667B009B6670 /* SecItemShimProxyMessages.h in Headers */, -- 7AA746D523593D8100095050 /* SecItemSPI.h in Headers */, - 570AB8F320AE3BD700B8BE87 /* SecKeyProxyStore.h in Headers */, - 514D9F5719119D35000063A7 /* ServicesController.h in Headers */, - 1AFDE65A1954A42B00C48FFA /* SessionState.h in Headers */, -@@ -9934,6 +9983,7 @@ +@@ -10112,6 +10162,7 @@ F430E94422473DFF005FE053 /* WebContentMode.h in Headers */, 31A505FA1680025500A930EB /* WebContextClient.h in Headers */, BC09B8F9147460F7005F5625 /* WebContextConnectionClient.h in Headers */, @@ -6363,7 +6388,7 @@ index 5cb81ccc202..2bf6772abb6 100644 BCDE059B11CDA8AE00E41AF1 /* WebContextInjectedBundleClient.h in Headers */, 51871B5C127CB89D00F76232 /* WebContextMenu.h in Headers */, BC032D7710F4378D0058C15A /* WebContextMenuClient.h in Headers */, -@@ -10167,6 +10217,7 @@ +@@ -10345,6 +10396,7 @@ BCD25F1711D6BDE100169B0E /* WKBundleFrame.h in Headers */, BCF049E611FE20F600F86A58 /* WKBundleFramePrivate.h in Headers */, BC49862F124D18C100D834E1 /* WKBundleHitTestResult.h in Headers */, @@ -6371,7 +6396,7 @@ index 5cb81ccc202..2bf6772abb6 100644 BC204EF211C83EC8008F3375 /* WKBundleInitialize.h in Headers */, 65B86F1E12F11DE300B7DD8A /* WKBundleInspector.h in Headers */, 1A8B66B41BC45B010082DF77 /* WKBundleMac.h in Headers */, -@@ -10215,6 +10266,7 @@ +@@ -10393,6 +10445,7 @@ 5C795D71229F3757003FF1C4 /* WKContextMenuElementInfoPrivate.h in Headers */, 51A555F6128C6C47009ABCEC /* WKContextMenuItem.h in Headers */, 51A55601128C6D92009ABCEC /* WKContextMenuItemTypes.h in Headers */, @@ -6379,7 +6404,7 @@ index 5cb81ccc202..2bf6772abb6 100644 A1EA02381DABFF7E0096021F /* WKContextMenuListener.h in Headers */, BCC938E11180DE440085E5FE /* WKContextPrivate.h in Headers */, 9FB5F395169E6A80002C25BF /* WKContextPrivateMac.h in Headers */, -@@ -10363,6 +10415,7 @@ +@@ -10542,6 +10595,7 @@ 1AB8A1F818400BB800E9AE69 /* WKPageContextMenuClient.h in Headers */, 8372DB251A674C8F00C697C5 /* WKPageDiagnosticLoggingClient.h in Headers */, 1AB8A1F418400B8F00E9AE69 /* WKPageFindClient.h in Headers */, @@ -6387,7 +6412,7 @@ index 5cb81ccc202..2bf6772abb6 100644 1AB8A1F618400B9D00E9AE69 /* WKPageFindMatchesClient.h in Headers */, 1AB8A1F018400B0000E9AE69 /* WKPageFormClient.h in Headers */, BC7B633712A45ABA00D174A4 /* WKPageGroup.h in Headers */, -@@ -11318,6 +11371,7 @@ +@@ -11567,6 +11621,7 @@ 2D92A781212B6A7100F493FD /* MessageReceiverMap.cpp in Sources */, 2D92A782212B6A7100F493FD /* MessageSender.cpp in Sources */, 2D92A77A212B6A6100F493FD /* Module.cpp in Sources */, @@ -6395,7 +6420,7 @@ index 5cb81ccc202..2bf6772abb6 100644 57B826452304F14000B72EB0 /* NearFieldSoftLink.mm in Sources */, 2D913443212CF9F000128AFD /* NetscapeBrowserFuncs.cpp in Sources */, 2D913444212CF9F000128AFD /* NetscapePlugin.cpp in Sources */, -@@ -11342,6 +11396,7 @@ +@@ -11591,6 +11646,7 @@ 1A2D8439127F65D5001EB962 /* NPObjectMessageReceiverMessageReceiver.cpp in Sources */, 2D92A792212B6AD400F493FD /* NPObjectProxy.cpp in Sources */, 2D92A793212B6AD400F493FD /* NPRemoteObjectMap.cpp in Sources */, @@ -6403,7 +6428,7 @@ index 5cb81ccc202..2bf6772abb6 100644 2D913447212CF9F000128AFD /* NPRuntimeObjectMap.cpp in Sources */, 2D913448212CF9F000128AFD /* NPRuntimeUtilities.cpp in Sources */, 2D92A794212B6AD400F493FD /* NPVariantData.cpp in Sources */, -@@ -11381,11 +11436,13 @@ +@@ -11630,11 +11686,13 @@ A1ADAFB62368E6A8009CB776 /* SharedMemory.cpp in Sources */, 2DE6943D18BD2A68005C15E5 /* SmartMagnificationControllerMessageReceiver.cpp in Sources */, 1A334DED16DE8F88006A8E38 /* StorageAreaMapMessageReceiver.cpp in Sources */, @@ -6455,10 +6480,10 @@ index 6cbd7fad5ff..176c46f186b 100644 void connect(Inspector::FrontendChannel::ConnectionType) override; void disconnect() override; diff --git a/Source/WebKit/WebProcess/WebProcess.cpp b/Source/WebKit/WebProcess/WebProcess.cpp -index 0c92cd9b030..0ed5b37c4d5 100644 +index bc66e49ccde..a16dd94b7be 100644 --- a/Source/WebKit/WebProcess/WebProcess.cpp +++ b/Source/WebKit/WebProcess/WebProcess.cpp -@@ -628,7 +628,8 @@ void WebProcess::setCacheModel(CacheModel cacheModel) +@@ -634,7 +634,8 @@ void WebProcess::setCacheModel(CacheModel cacheModel) unsigned cacheMaxDeadCapacity = 0; Seconds deadDecodedDataDeletionInterval; unsigned backForwardCacheSize = 0; @@ -7069,5 +7094,5 @@ index 2d183d39412..d94d4f06fc5 100644 webkit_web_context_set_tls_errors_policy(webContext, WEBKIT_TLS_ERRORS_POLICY_IGNORE); -- -2.24.0 +2.22.1 From 51ca756efe296e5d3489c9a86462b086557014aa Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 5 Dec 2019 14:11:48 -0800 Subject: [PATCH 02/42] chore: encapsulate target business in Browser class (#151) Page and BrowserContext are now closer to be reused between browsers. --- src/chromium/Browser.ts | 16 ++++++++-- src/chromium/BrowserContext.ts | 14 ++------- src/chromium/Page.ts | 33 +++++++++++--------- src/chromium/Screenshotter.ts | 2 +- src/chromium/Target.ts | 21 +++++++++---- src/chromium/features/chromium.ts | 2 +- src/firefox/Browser.ts | 30 +++++++++---------- src/firefox/Page.ts | 38 +++++++++++++---------- src/webkit/Browser.ts | 39 +++++++++++++++--------- src/webkit/FrameManager.ts | 2 +- src/webkit/Page.ts | 50 ++++++++++++++----------------- src/webkit/Target.ts | 23 +++++++++++--- 12 files changed, 155 insertions(+), 115 deletions(-) diff --git a/src/chromium/Browser.ts b/src/chromium/Browser.ts index 2c65fdb9c3..dd7585b0a0 100644 --- a/src/chromium/Browser.ts +++ b/src/chromium/Browser.ts @@ -119,7 +119,7 @@ export class Browser extends EventEmitter { const target = this._targets.get(event.targetId); target._initializedCallback(false); this._targets.delete(event.targetId); - target._closedCallback(); + target._didClose(); if (await target._initializedPromise) this.chromium.emit(Events.Chromium.TargetDestroyed, target); } @@ -146,14 +146,24 @@ export class Browser extends EventEmitter { return page; } - async _closeTarget(target: Target) { - await this._client.send('Target.closeTarget', { targetId: target._targetId }); + async _closePage(page: Page) { + await this._client.send('Target.closeTarget', { targetId: Target.fromPage(page)._targetId }); } _allTargets(): Target[] { return Array.from(this._targets.values()).filter(target => target._isInitialized); } + async _pages(context: BrowserContext): Promise { + const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page'); + const pages = await Promise.all(targets.map(target => target.page())); + return pages.filter(page => !!page); + } + + async _activatePage(page: Page) { + await page._client.send('Target.activateTarget', {targetId: Target.fromPage(page)._targetId}); + } + async _waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise { const { timeout = 30000 diff --git a/src/chromium/BrowserContext.ts b/src/chromium/BrowserContext.ts index 7ae966dba2..ec6314ff73 100644 --- a/src/chromium/BrowserContext.ts +++ b/src/chromium/BrowserContext.ts @@ -21,7 +21,6 @@ import { Browser } from './Browser'; import { CDPSession } from './Connection'; import { Permissions } from './features/permissions'; import { Page } from './Page'; -import { Target } from './Target'; export class BrowserContext { readonly permissions: Permissions; @@ -35,17 +34,8 @@ export class BrowserContext { this.permissions = new Permissions(client, contextId); } - _targets(): Target[] { - return this._browser._allTargets().filter(target => target.browserContext() === this); - } - - async pages(): Promise { - const pages = await Promise.all( - this._targets() - .filter(target => target.type() === 'page') - .map(target => target.page()) - ); - return pages.filter(page => !!page); + pages(): Promise { + return this._browser._pages(this); } isIncognito(): boolean { diff --git a/src/chromium/Page.ts b/src/chromium/Page.ts index dcd20a7b84..ae93268ab5 100644 --- a/src/chromium/Page.ts +++ b/src/chromium/Page.ts @@ -35,7 +35,6 @@ import { RawMouseImpl, RawKeyboardImpl } from './Input'; import { NetworkManagerEvents } from './NetworkManager'; import { Protocol } from './protocol'; import { getExceptionMessage, releaseObject } from './protocolHelper'; -import { Target } from './Target'; import * as input from '../input'; import * as types from '../types'; import * as frames from '../frames'; @@ -58,8 +57,10 @@ export type Viewport = { export class Page extends EventEmitter { private _closed = false; + private _closedCallback: () => void; + private _closedPromise: Promise; _client: CDPSession; - _target: Target; + private _browserContext: BrowserContext; private _keyboard: input.Keyboard; private _mouse: input.Mouse; private _timeoutSettings: TimeoutSettings; @@ -79,18 +80,19 @@ export class Page extends EventEmitter { private _disconnectPromise: Promise | undefined; private _emulatedMediaType: string | undefined; - static async create(client: CDPSession, target: Target, ignoreHTTPSErrors: boolean, defaultViewport: Viewport | null, screenshotter: Screenshotter): Promise { - const page = new Page(client, target, ignoreHTTPSErrors, screenshotter); + static async create(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean, defaultViewport: Viewport | null, screenshotter: Screenshotter): Promise { + const page = new Page(client, browserContext, ignoreHTTPSErrors, screenshotter); await page._initialize(); if (defaultViewport) await page.setViewport(defaultViewport); return page; } - constructor(client: CDPSession, target: Target, ignoreHTTPSErrors: boolean, screenshotter: Screenshotter) { + constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean, screenshotter: Screenshotter) { super(); this._client = client; - this._target = target; + this._closedPromise = new Promise(f => this._closedCallback = f); + this._browserContext = browserContext; this._keyboard = new input.Keyboard(new RawKeyboardImpl(client)); this._mouse = new input.Mouse(new RawMouseImpl(client), this._keyboard); this._timeoutSettings = new TimeoutSettings(); @@ -134,10 +136,13 @@ export class Page extends EventEmitter { client.on('Inspector.targetCrashed', event => this._onTargetCrashed()); client.on('Log.entryAdded', event => this._onLogEntryAdded(event)); client.on('Page.fileChooserOpened', event => this._onFileChooserOpened(event)); - this._target._isClosedPromise.then(() => { - this.emit(Events.Page.Close); - this._closed = true; - }); + } + + _didClose() { + assert(!this._closed, 'Page closed twice'); + this._closed = true; + this.emit(Events.Page.Close); + this._closedCallback(); } async _initialize() { @@ -179,11 +184,11 @@ export class Page extends EventEmitter { } browser(): Browser { - return this._target.browser(); + return this._browserContext.browser(); } browserContext(): BrowserContext { - return this._target.browserContext(); + return this._browserContext; } _onTargetCrashed() { @@ -518,8 +523,8 @@ export class Page extends EventEmitter { if (runBeforeUnload) { await this._client.send('Page.close'); } else { - await this.browser()._closeTarget(this._target); - await this._target._isClosedPromise; + await this.browser()._closePage(this); + await this._closedPromise; } } diff --git a/src/chromium/Screenshotter.ts b/src/chromium/Screenshotter.ts index fe5666b184..2740233480 100644 --- a/src/chromium/Screenshotter.ts +++ b/src/chromium/Screenshotter.ts @@ -85,7 +85,7 @@ export class Screenshotter { } private async _screenshot(page: Page, format: 'png' | 'jpeg', options: ScreenshotOptions): Promise { - await page._client.send('Target.activateTarget', {targetId: page._target._targetId}); + await page.browser()._activatePage(page); let clip = options.clip ? processClip(options.clip) : undefined; const viewport = page.viewport(); diff --git a/src/chromium/Target.ts b/src/chromium/Target.ts index 922eacb1b2..4e33301837 100644 --- a/src/chromium/Target.ts +++ b/src/chromium/Target.ts @@ -24,6 +24,8 @@ import { Page, Viewport } from './Page'; import { Protocol } from './protocol'; import { Screenshotter } from './Screenshotter'; +const targetSymbol = Symbol('target'); + export class Target { private _targetInfo: Protocol.Target.TargetInfo; private _browserContext: BrowserContext; @@ -36,10 +38,12 @@ export class Target { private _workerPromise: Promise | null = null; _initializedPromise: Promise; _initializedCallback: (value?: unknown) => void; - _isClosedPromise: Promise; - _closedCallback: (value?: unknown) => void; _isInitialized: boolean; + static fromPage(page: Page): Target { + return (page as any)[targetSymbol]; + } + constructor( targetInfo: Protocol.Target.TargetInfo, browserContext: BrowserContext, @@ -67,16 +71,23 @@ export class Target { openerPage.emit(Events.Page.Popup, popupPage); return true; }); - this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill); this._isInitialized = this._targetInfo.type !== 'page' || this._targetInfo.url !== ''; if (this._isInitialized) this._initializedCallback(true); } + _didClose() { + if (this._pagePromise) + this._pagePromise.then(page => page._didClose()); + } + async page(): Promise { if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) { - this._pagePromise = this._sessionFactory() - .then(client => Page.create(client, this, this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotter)); + this._pagePromise = this._sessionFactory().then(async client => { + const page = await Page.create(client, this._browserContext, this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotter); + page[targetSymbol] = this; + return page; + }); } return this._pagePromise; } diff --git a/src/chromium/features/chromium.ts b/src/chromium/features/chromium.ts index 613552b3b4..8aa84f5ab3 100644 --- a/src/chromium/features/chromium.ts +++ b/src/chromium/features/chromium.ts @@ -92,7 +92,7 @@ export class Chromium extends EventEmitter { } pageTarget(page: Page): Target { - return page._target; + return Target.fromPage(page); } waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise { diff --git a/src/firefox/Browser.ts b/src/firefox/Browser.ts index 852e604dbf..7318474fd3 100644 --- a/src/firefox/Browser.ts +++ b/src/firefox/Browser.ts @@ -150,6 +150,12 @@ export class Browser extends EventEmitter { return Array.from(this._targets.values()); } + async _pages(context: BrowserContext): Promise { + const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page'); + const pages = await Promise.all(targets.map(target => target.page())); + return pages.filter(page => !!page); + } + async _onTargetCreated({targetId, url, browserContextId, openerId, type}) { const context = browserContextId ? this._contexts.get(browserContextId) : this._defaultContext; const target = new Target(this._connection, this, context, targetId, type, url, openerId); @@ -166,7 +172,7 @@ export class Browser extends EventEmitter { _onTargetDestroyed({targetId}) { const target = this._targets.get(targetId); this._targets.delete(targetId); - target._closedCallback(); + target._didClose(); } _onTargetInfoChanged({targetId, url}) { @@ -189,8 +195,6 @@ export class Target { private _type: 'page' | 'browser'; _url: string; private _openerId: string; - _isClosedPromise: Promise; - _closedCallback: (value?: unknown) => void; constructor(connection: any, browser: Browser, context: BrowserContext, targetId: string, type: 'page' | 'browser', url: string, openerId: string | undefined) { this._browser = browser; @@ -200,9 +204,12 @@ export class Target { this._type = type; this._url = url; this._openerId = openerId; - this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill); } + _didClose() { + if (this._pagePromise) + this._pagePromise.then(page => page._didClose()); + } opener(): Target | null { return this._openerId ? this._browser._targets.get(this._openerId) : null; @@ -225,7 +232,7 @@ export class Target { async page() { if (this._type === 'page' && !this._pagePromise) { const session = await this._connection.createSession(this._targetId); - this._pagePromise = Page.create(session, this, this._browser._defaultViewport); + this._pagePromise = Page.create(session, this._context, this._browser._defaultViewport); } return this._pagePromise; } @@ -248,17 +255,8 @@ export class BrowserContext { this.permissions = new Permissions(connection, browserContextId); } - _targets(): Array { - return this._browser._allTargets().filter(target => target.browserContext() === this); - } - - async pages(): Promise> { - const pages = await Promise.all( - this._targets() - .filter(target => target.type() === 'page') - .map(target => target.page()) - ); - return pages.filter(page => !!page); + pages(): Promise { + return this._browser._pages(this); } isIncognito(): boolean { diff --git a/src/firefox/Page.ts b/src/firefox/Page.ts index f0de1717f3..7ffd457c38 100644 --- a/src/firefox/Page.ts +++ b/src/firefox/Page.ts @@ -21,7 +21,7 @@ import * as mime from 'mime'; import { TimeoutError } from '../Errors'; import { assert, debugError, helper, RegisteredListener } from '../helper'; import { TimeoutSettings } from '../TimeoutSettings'; -import { BrowserContext, Target } from './Browser'; +import { BrowserContext } from './Browser'; import { JugglerSession, JugglerSessionEvents } from './Connection'; import { Events } from './events'; import { Accessibility } from './features/accessibility'; @@ -44,12 +44,14 @@ const writeFileAsync = helper.promisify(fs.writeFile); export class Page extends EventEmitter { private _timeoutSettings: TimeoutSettings; private _session: JugglerSession; - private _target: Target; + private _browserContext: BrowserContext; private _keyboard: input.Keyboard; private _mouse: input.Mouse; readonly accessibility: Accessibility; readonly interception: Interception; private _closed: boolean; + private _closedCallback: () => void; + private _closedPromise: Promise; private _pageBindings: Map; private _networkManager: NetworkManager; _frameManager: FrameManager; @@ -59,8 +61,8 @@ export class Page extends EventEmitter { private _disconnectPromise: Promise; private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>(); - static async create(session: JugglerSession, target: Target, defaultViewport: Viewport | null) { - const page = new Page(session, target); + static async create(session: JugglerSession, browserContext: BrowserContext, defaultViewport: Viewport | null) { + const page = new Page(session, browserContext); await Promise.all([ session.send('Runtime.enable'), session.send('Network.enable'), @@ -73,15 +75,16 @@ export class Page extends EventEmitter { return page; } - constructor(session: JugglerSession, target: Target) { + constructor(session: JugglerSession, browserContext: BrowserContext) { super(); this._timeoutSettings = new TimeoutSettings(); this._session = session; - this._target = target; + this._browserContext = browserContext; this._keyboard = new input.Keyboard(new RawKeyboardImpl(session)); this._mouse = new input.Mouse(new RawMouseImpl(session), this._keyboard); this.accessibility = new Accessibility(session); this._closed = false; + this._closedPromise = new Promise(f => this._closedCallback = f); this._pageBindings = new Map(); this._networkManager = new NetworkManager(session); this._frameManager = new FrameManager(session, this, this._networkManager, this._timeoutSettings); @@ -104,13 +107,16 @@ export class Page extends EventEmitter { helper.addEventListener(this._networkManager, NetworkManagerEvents.RequestFailed, request => this.emit(Events.Page.RequestFailed, request)), ]; this._viewport = null; - this._target._isClosedPromise.then(() => { - this._closed = true; - this._frameManager.dispose(); - this._networkManager.dispose(); - helper.removeEventListeners(this._eventListeners); - this.emit(Events.Page.Close); - }); + } + + _didClose() { + assert(!this._closed, 'Page closed twice'); + this._closed = true; + this._frameManager.dispose(); + this._networkManager.dispose(); + helper.removeEventListeners(this._eventListeners); + this.emit(Events.Page.Close); + this._closedCallback(); } async setExtraHTTPHeaders(headers) { @@ -250,7 +256,7 @@ export class Page extends EventEmitter { } browserContext(): BrowserContext { - return this._target.browserContext(); + return this._browserContext; } _onUncaughtError(params) { @@ -288,7 +294,7 @@ export class Page extends EventEmitter { } browser() { - return this._target.browser(); + return this._browserContext.browser(); } url() { @@ -535,7 +541,7 @@ export class Page extends EventEmitter { } = options; await this._session.send('Page.close', { runBeforeUnload }); if (!runBeforeUnload) - await this._target._isClosedPromise; + await this._closedPromise; } async content() { diff --git a/src/webkit/Browser.ts b/src/webkit/Browser.ts index f37b976238..e54010b9aa 100644 --- a/src/webkit/Browser.ts +++ b/src/webkit/Browser.ts @@ -17,7 +17,7 @@ import * as childProcess from 'child_process'; import { EventEmitter } from 'events'; -import { assert, helper, RegisteredListener } from '../helper'; +import { assert, helper, RegisteredListener, debugError } from '../helper'; import { filterCookies, NetworkCookie, rewriteCookies, SetNetworkCookieParam } from '../network'; import { Connection } from './Connection'; import { Page, Viewport } from './Page'; @@ -167,7 +167,23 @@ export class Browser extends EventEmitter { _onTargetDestroyed({targetId}) { const target = this._targets.get(targetId); this._targets.delete(targetId); - target._closedCallback(); + target._didClose(); + } + + _closePage(page: Page) { + this._connection.send('Target.close', { + targetId: Target.fromPage(page)._targetId + }).catch(debugError); + } + + async _pages(context: BrowserContext): Promise { + const targets = this.targets().filter(target => target.browserContext() === context && target.type() === 'page'); + const pages = await Promise.all(targets.map(target => target.page())); + return pages.filter(page => !!page); + } + + async _activatePage(page: Page): Promise { + await this._connection.send('Target.activate', { targetId: Target.fromPage(page)._targetId }); } async _onProvisionalTargetCommitted({oldTargetId, newTargetId}) { @@ -177,8 +193,12 @@ export class Browser extends EventEmitter { const page = await oldTarget._pagePromise; const newTarget = this._targets.get(newTargetId); const newSession = this._connection.session(newTargetId); - page._swapTargetOnNavigation(newSession, newTarget); + page._swapSessionOnNavigation(newSession); newTarget._pagePromise = oldTarget._pagePromise; + newTarget._adoptPage(page); + // Old target should not be accessed by anyone. Reset page promise so that + // old target does not close the page on connection reset. + oldTarget._pagePromise = null; } disconnect() { @@ -204,17 +224,8 @@ export class BrowserContext { this._id = contextId; } - _targets(): Target[] { - return this._browser.targets().filter(target => target.browserContext() === this); - } - - async pages(): Promise { - const pages = await Promise.all( - this._targets() - .filter(target => target.type() === 'page') - .map(target => target.page()) - ); - return pages.filter(page => !!page); + pages(): Promise { + return this._browser._pages(this); } isIncognito(): boolean { diff --git a/src/webkit/FrameManager.ts b/src/webkit/FrameManager.ts index 09425e5b1d..535aa68d83 100644 --- a/src/webkit/FrameManager.ts +++ b/src/webkit/FrameManager.ts @@ -91,7 +91,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate { ]; } - async _swapTargetOnNavigation(newSession) { + async _swapSessionOnNavigation(newSession) { helper.removeEventListeners(this._sessionListeners); this.disconnectFromTarget(); this._session = newSession; diff --git a/src/webkit/Page.ts b/src/webkit/Page.ts index 2b0d142b08..266baaebf5 100644 --- a/src/webkit/Page.ts +++ b/src/webkit/Page.ts @@ -28,7 +28,6 @@ import { FrameManager, FrameManagerEvents } from './FrameManager'; import { RawKeyboardImpl, RawMouseImpl } from './Input'; import { NetworkManagerEvents } from './NetworkManager'; import { Protocol } from './protocol'; -import { Target } from './Target'; import { TaskQueue } from './TaskQueue'; import * as input from '../input'; import * as types from '../types'; @@ -48,8 +47,10 @@ export type Viewport = { export class Page extends EventEmitter { private _closed = false; + private _closedCallback: () => void; + private _closedPromise: Promise; _session: TargetSession; - private _target: Target; + private _browserContext: BrowserContext; private _keyboard: input.Keyboard; private _mouse: input.Mouse; private _timeoutSettings: TimeoutSettings; @@ -64,16 +65,17 @@ export class Page extends EventEmitter { private _emulatedMediaType: string | undefined; private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>(); - static async create(session: TargetSession, target: Target, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue): Promise { - const page = new Page(session, target, screenshotTaskQueue); + static async create(session: TargetSession, browserContext: BrowserContext, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue): Promise { + const page = new Page(session, browserContext, screenshotTaskQueue); await page._initialize(); if (defaultViewport) await page.setViewport(defaultViewport); return page; } - constructor(session: TargetSession, target: Target, screenshotTaskQueue: TaskQueue) { + constructor(session: TargetSession, browserContext: BrowserContext, screenshotTaskQueue: TaskQueue) { super(); + this._closedPromise = new Promise(f => this._closedCallback = f); this._keyboard = new input.Keyboard(new RawKeyboardImpl(session)); this._mouse = new input.Mouse(new RawMouseImpl(session), this._keyboard); this._timeoutSettings = new TimeoutSettings(); @@ -82,7 +84,7 @@ export class Page extends EventEmitter { this._screenshotTaskQueue = screenshotTaskQueue; this._setSession(session); - this._setTarget(target); + this._browserContext = browserContext; this._frameManager.on(FrameManagerEvents.FrameAttached, event => this.emit(Events.Page.FrameAttached, event)); this._frameManager.on(FrameManagerEvents.FrameDetached, event => this.emit(Events.Page.FrameDetached, event)); @@ -95,6 +97,13 @@ export class Page extends EventEmitter { networkManager.on(NetworkManagerEvents.RequestFinished, event => this.emit(Events.Page.RequestFinished, event)); } + _didClose() { + assert(!this._closed, 'Page closed twice'); + this._closed = true; + this.emit(Events.Page.Close); + this._closedCallback(); + } + async _initialize() { return Promise.all([ this._frameManager.initialize(), @@ -127,29 +136,18 @@ export class Page extends EventEmitter { event.defaultPrompt)); } - _setTarget(newTarget: Target) { - this._target = newTarget; - this._target._isClosedPromise.then(() => { - if (this._target !== newTarget) - return; - this.emit(Events.Page.Close); - this._closed = true; - }); - } - - async _swapTargetOnNavigation(newSession : TargetSession, newTarget : Target) { + async _swapSessionOnNavigation(newSession: TargetSession) { this._setSession(newSession); - this._setTarget(newTarget); - await this._frameManager._swapTargetOnNavigation(newSession); + await this._frameManager._swapSessionOnNavigation(newSession); await this._initialize().catch(e => debugError('failed to enable agents after swap: ' + e)); } browser(): Browser { - return this._target.browser(); + return this._browserContext.browser(); } browserContext(): BrowserContext { - return this._target.browserContext(); + return this._browserContext; } _onTargetCrashed() { @@ -419,7 +417,7 @@ export class Page extends EventEmitter { Object.assign(params, this._viewport); } const [, result] = await Promise.all([ - this._session._connection.send('Target.activate', { targetId: this._target._targetId }), + this.browser()._activatePage(this), this._session.send('Page.snapshotRect', params), ]).catch(e => { debugError('Failed to take screenshot: ' + e); @@ -437,12 +435,8 @@ export class Page extends EventEmitter { } async close() { - this.browser()._connection.send('Target.close', { - targetId: this._target._targetId - }).catch(e => { - debugError(e); - }); - await this._target._isClosedPromise; + this.browser()._closePage(this); + await this._closedPromise; } isClosed(): boolean { diff --git a/src/webkit/Target.ts b/src/webkit/Target.ts index 66c379b37b..785fe6119c 100644 --- a/src/webkit/Target.ts +++ b/src/webkit/Target.ts @@ -20,6 +20,8 @@ import { Browser, BrowserContext } from './Browser'; import { Page } from './Page'; import { Protocol } from './protocol'; +const targetSymbol = Symbol('target'); + export class Target { private _browserContext: BrowserContext; _targetId: string; @@ -28,11 +30,13 @@ export class Target { private _url: string; _initializedPromise: Promise; _initializedCallback: (value?: unknown) => void; - _isClosedPromise: Promise; - _closedCallback: (value?: unknown) => void; _isInitialized: boolean; _eventListeners: RegisteredListener[]; + static fromPage(page: Page): Target { + return (page as any)[targetSymbol]; + } + constructor(targetInfo: Protocol.Target.TargetInfo, browserContext: BrowserContext) { const {targetId, url, type} = targetInfo; this._browserContext = browserContext; @@ -41,13 +45,24 @@ export class Target { /** @type {?Promise} */ this._pagePromise = null; this._url = url; - this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill); + } + + _didClose() { + if (this._pagePromise) + this._pagePromise.then(page => page._didClose()); + } + + _adoptPage(page: Page) { + (page as any)[targetSymbol] = this; } async page(): Promise { if (this._type === 'page' && !this._pagePromise) { const session = this.browser()._connection.session(this._targetId); - this._pagePromise = Page.create(session, this, this.browser()._defaultViewport, this.browser()._screenshotTaskQueue); + this._pagePromise = Page.create(session, this._browserContext, this.browser()._defaultViewport, this.browser()._screenshotTaskQueue).then(page => { + this._adoptPage(page); + return page; + }); } return this._pagePromise; } From 929a5944bd6acaa74e698ea12ef1c457c4d3dfae Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 5 Dec 2019 14:12:22 -0800 Subject: [PATCH 03/42] test: fix chromium tests (#155) 3 chromium tests were failing on Linux. --- test/chromiumonly.spec.js | 16 ++++++++++++---- test/click.spec.js | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/test/chromiumonly.spec.js b/test/chromiumonly.spec.js index 2f6f488f1c..b0adeacc6e 100644 --- a/test/chromiumonly.spec.js +++ b/test/chromiumonly.spec.js @@ -14,7 +14,15 @@ * limitations under the License. */ -const utils = require('./utils'); +const {waitEvent} = require('./utils'); +const util = require('util'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const rmAsync = util.promisify(require('rimraf')); +const mkdtempAsync = util.promisify(fs.mkdtemp); + +const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOptions, playwright}) { const {describe, xdescribe, fdescribe} = testRunner; @@ -162,7 +170,7 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp remoteBrowser2.on('disconnected', () => ++disconnectedRemote2); await Promise.all([ - utils.waitEvent(remoteBrowser2, 'disconnected'), + waitEvent(remoteBrowser2, 'disconnected'), remoteBrowser2.disconnect(), ]); @@ -171,8 +179,8 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp expect(disconnectedRemote2).toBe(1); await Promise.all([ - utils.waitEvent(remoteBrowser1, 'disconnected'), - utils.waitEvent(originalBrowser, 'disconnected'), + waitEvent(remoteBrowser1, 'disconnected'), + waitEvent(originalBrowser, 'disconnected'), originalBrowser.close(), ]); diff --git a/test/click.spec.js b/test/click.spec.js index d80c0217c5..18738cafc2 100644 --- a/test/click.spec.js +++ b/test/click.spec.js @@ -301,6 +301,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME it('should click the button with em border with relative point', async({page, server}) => { await page.goto(server.PREFIX + '/input/button.html'); await page.$eval('button', button => button.style.borderWidth = '2em'); + await page.$eval('button', button => button.style.fontSize = '12px'); await page.click('button', { relativePoint: { x: 20, y: 10 } }); expect(await page.evaluate(() => result)).toBe('Clicked'); expect(await page.evaluate(() => offsetX)).toBe(20); From b84d3c6cbe84923c26e07a2a96ab66307b165f1d Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 5 Dec 2019 15:13:18 -0700 Subject: [PATCH 04/42] chore: do not accumulate protocol messages for debugging (#150) --- src/webkit/Connection.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/webkit/Connection.ts b/src/webkit/Connection.ts index 43386a6089..887f0a746b 100644 --- a/src/webkit/Connection.ts +++ b/src/webkit/Connection.ts @@ -99,12 +99,9 @@ export class Connection extends EventEmitter { _dispatchTargetMessageToSession(object: {method: string, params: any}) { if (object.method === 'Target.targetCreated') { const {targetId, type} = object.params.targetInfo; - // FIXME: this is a workaround for cross-origin navigation in WebKit. - // console.log(`[${targetId}] ${object.method}`); const session = new TargetSession(this, type, targetId); this._sessions.set(targetId, session); } else if (object.method === 'Target.targetDestroyed') { - // console.log(`[${object.params.targetId}] ${object.method}`); const session = this._sessions.get(object.params.targetId); if (session) { // FIXME: this is a workaround for cross-origin navigation in WebKit. @@ -161,8 +158,6 @@ export class TargetSession extends EventEmitter { private _callbacks = new Map void, reject: (e: Error) => void, error: Error, method: string}>(); private _targetType: string; private _sessionId: string; - private _out = []; - private _in = []; on: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; addListener: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; off: (event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this; @@ -189,7 +184,6 @@ export class TargetSession extends EventEmitter { params }; debugWrappedMessage('SEND â–º ' + JSON.stringify(messageObj, null, 2)); - this._out.push(messageObj); // Serialize message before adding callback in case JSON throws. const message = JSON.stringify(messageObj); const result = new Promise((resolve, reject) => { @@ -210,7 +204,6 @@ export class TargetSession extends EventEmitter { _dispatchMessageFromTarget(message: string) { const object = JSON.parse(message); debugWrappedMessage('â—€ RECV ' + JSON.stringify(object, null, 2)); - this._in.push(object); if (object.id && this._callbacks.has(object.id)) { const callback = this._callbacks.get(object.id); this._callbacks.delete(object.id); @@ -220,7 +213,6 @@ export class TargetSession extends EventEmitter { callback.resolve(object.result); } else { assert(!object.id); - // console.log(`[${this._sessionId}] ${object.method}`); this.emit(object.method, object.params); } } From 69d2d81c05ea5669595fbd9fea014617fc7dfc61 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 5 Dec 2019 14:44:35 -0800 Subject: [PATCH 05/42] feat(webkit): emulate background and device scale (#157) --- browser_patches/webkit/BUILD_NUMBER | 2 +- .../webkit/patches/0001-chore-bootstrap.patch | 162 +++++++++++++----- 2 files changed, 119 insertions(+), 45 deletions(-) diff --git a/browser_patches/webkit/BUILD_NUMBER b/browser_patches/webkit/BUILD_NUMBER index 9951021cfe..7cf69b1871 100644 --- a/browser_patches/webkit/BUILD_NUMBER +++ b/browser_patches/webkit/BUILD_NUMBER @@ -1 +1 @@ -1013 +1014 diff --git a/browser_patches/webkit/patches/0001-chore-bootstrap.patch b/browser_patches/webkit/patches/0001-chore-bootstrap.patch index 26f468f191..e2ddfcd9a7 100644 --- a/browser_patches/webkit/patches/0001-chore-bootstrap.patch +++ b/browser_patches/webkit/patches/0001-chore-bootstrap.patch @@ -1,6 +1,6 @@ -From b33decbe420d2874925adee8e4dd36bc9255072c Mon Sep 17 00:00:00 2001 -From: Andrey Lushnikov -Date: Thu, 5 Dec 2019 13:50:00 -0800 +From 058f2450a6d88f65e939bf61b9b54a15ac0cbfd6 Mon Sep 17 00:00:00 2001 +From: Pavel Feldman +Date: Thu, 5 Dec 2019 14:38:40 -0800 Subject: [PATCH] chore: bootstrap --- @@ -14,23 +14,24 @@ Subject: [PATCH] chore: bootstrap .../inspector/protocol/Browser.json | 106 ++++ .../inspector/protocol/DOM.json | 39 ++ .../inspector/protocol/Dialog.json | 36 ++ - .../inspector/protocol/Emulation.json | 21 + + .../inspector/protocol/Emulation.json | 22 + .../inspector/protocol/Input.json | 160 ++++++ - .../inspector/protocol/Page.json | 90 +++- + .../inspector/protocol/Page.json | 97 +++- .../inspector/protocol/Target.json | 20 +- Source/WebCore/html/FileInputType.cpp | 6 + .../inspector/InspectorInstrumentation.cpp | 14 +- .../inspector/InspectorInstrumentation.h | 21 + - .../inspector/agents/InspectorDOMAgent.cpp | 103 ++++ - .../inspector/agents/InspectorDOMAgent.h | 2 + - .../inspector/agents/InspectorPageAgent.cpp | 509 +++++++++++++++++- - .../inspector/agents/InspectorPageAgent.h | 18 +- + .../inspector/agents/InspectorDOMAgent.cpp | 108 +++- + .../inspector/agents/InspectorDOMAgent.h | 4 + + .../agents/InspectorDOMStorageAgent.h | 1 + + .../inspector/agents/InspectorPageAgent.cpp | 523 +++++++++++++++++- + .../inspector/agents/InspectorPageAgent.h | 19 +- .../agents/page/PageRuntimeAgent.cpp | 14 +- Source/WebCore/loader/FrameLoader.cpp | 1 + Source/WebCore/page/History.cpp | 1 + .../WebCore/platform/PlatformKeyboardEvent.h | 2 + - .../platform/gtk/PlatformKeyboardEventGtk.cpp | 242 +++++++++ - .../libwpe/PlatformKeyboardEventLibWPE.cpp | 240 +++++++++ + .../platform/gtk/PlatformKeyboardEventGtk.cpp | 242 ++++++++ + .../libwpe/PlatformKeyboardEventLibWPE.cpp | 240 ++++++++ .../soup/NetworkStorageSessionSoup.cpp | 9 +- .../WebKit/NetworkProcess/NetworkProcess.cpp | 30 +- Source/WebKit/NetworkProcess/NetworkProcess.h | 5 + @@ -87,7 +88,7 @@ Subject: [PATCH] chore: bootstrap .../Mock/MockAuthenticatorManager.cpp | 4 +- .../UIProcess/WebPageInspectorController.cpp | 56 +- .../UIProcess/WebPageInspectorController.h | 8 + - .../WebPageInspectorEmulationAgent.cpp | 47 ++ + .../WebPageInspectorEmulationAgent.cpp | 48 ++ .../WebPageInspectorEmulationAgent.h | 42 ++ .../UIProcess/WebPageInspectorInputAgent.cpp | 235 ++++++++ .../UIProcess/WebPageInspectorInputAgent.h | 54 ++ @@ -106,7 +107,7 @@ Subject: [PATCH] chore: bootstrap .../WebKit/UIProcess/mac/PageClientImplMac.mm | 5 + .../mac/WebPageInspectorEmulationAgentMac.mm | 21 + .../mac/WebPageInspectorInputAgentMac.mm | 14 + - .../mac/WebPageInspectorTargetProxyMac.mm | 18 + + .../mac/WebPageInspectorTargetProxyMac.mm | 20 + .../wpe/WebPageInspectorEmulationAgentWPE.cpp | 18 + .../wpe/WebPageInspectorInputAgentWPE.cpp | 76 +++ .../wpe/WebPageInspectorTargetProxyWPE.cpp | 18 + @@ -122,7 +123,7 @@ Subject: [PATCH] chore: bootstrap .../mac/WK2BrowserWindowController.h | 3 + .../mac/WK2BrowserWindowController.m | 37 +- Tools/MiniBrowser/wpe/main.cpp | 37 ++ - 118 files changed, 4678 insertions(+), 75 deletions(-) + 119 files changed, 4710 insertions(+), 77 deletions(-) create mode 100644 Source/JavaScriptCore/inspector/protocol/Browser.json create mode 100644 Source/JavaScriptCore/inspector/protocol/Dialog.json create mode 100644 Source/JavaScriptCore/inspector/protocol/Emulation.json @@ -695,10 +696,10 @@ index 00000000000..79edea03fed +} diff --git a/Source/JavaScriptCore/inspector/protocol/Emulation.json b/Source/JavaScriptCore/inspector/protocol/Emulation.json new file mode 100644 -index 00000000000..af0f39e5249 +index 00000000000..759390956ea --- /dev/null +++ b/Source/JavaScriptCore/inspector/protocol/Emulation.json -@@ -0,0 +1,21 @@ +@@ -0,0 +1,22 @@ +{ + "domain": "Emulation", + "availability": ["web"], @@ -708,7 +709,8 @@ index 00000000000..af0f39e5249 + "description": "Overrides device metrics with provided values.", + "parameters": [ + { "name": "width", "type": "integer" }, -+ { "name": "height", "type": "integer" } ++ { "name": "height", "type": "integer" }, ++ { "name": "deviceScaleFactor", "type": "number" } + ] + }, + { @@ -887,7 +889,7 @@ index 00000000000..79bbe73b0df + ] +} diff --git a/Source/JavaScriptCore/inspector/protocol/Page.json b/Source/JavaScriptCore/inspector/protocol/Page.json -index 367d1f235a8..b2ed9177528 100644 +index 367d1f235a8..d7fa0aa81c5 100644 --- a/Source/JavaScriptCore/inspector/protocol/Page.json +++ b/Source/JavaScriptCore/inspector/protocol/Page.json @@ -108,6 +108,40 @@ @@ -953,7 +955,7 @@ index 367d1f235a8..b2ed9177528 100644 ] }, { -@@ -288,6 +331,27 @@ +@@ -288,6 +331,34 @@ "returns": [ { "name": "data", "type": "string", "description": "Base64-encoded web archive." } ] @@ -977,11 +979,18 @@ index 367d1f235a8..b2ed9177528 100644 + "description": "Intercepts file chooser dialog", + "parameters": [ + { "name": "enabled", "type": "boolean", "description": "True to enable." } ++ ] ++ }, ++ { ++ "name": "setDefaultBackgroundColorOverride", ++ "description": "Sets or clears an override of the default background color of the frame. This override is used if the content does not specify one.", ++ "parameters": [ ++ { "name": "color", "$ref": "DOM.RGBAColor", "optional": true, "description": "RGBA of the default background color. If not specified, any existing override will be cleared." } + ] } ], "events": [ -@@ -346,12 +410,36 @@ +@@ -346,12 +417,36 @@ { "name": "frameId", "$ref": "Network.FrameId", "description": "Id of the frame that has cleared its scheduled navigation." } ] }, @@ -1194,7 +1203,7 @@ index 6698431f316..486a6781d81 100644 { return context ? instrumentingAgentsForContext(*context) : nullptr; diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp -index aecc79bc0ca..57ce50c1f94 100644 +index aecc79bc0ca..a5e1de17d9e 100644 --- a/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.cpp @@ -61,12 +61,16 @@ @@ -1228,7 +1237,26 @@ index aecc79bc0ca..57ce50c1f94 100644 #include "StaticNodeList.h" #include "StyleProperties.h" #include "StyleResolver.h" -@@ -1475,6 +1481,61 @@ void InspectorDOMAgent::setInspectedNode(ErrorString& errorString, int nodeId) +@@ -128,7 +134,8 @@ using namespace HTMLNames; + static const size_t maxTextSize = 10000; + static const UChar ellipsisUChar[] = { 0x2026, 0 }; + +-static Color parseColor(const JSON::Object* colorObject) ++// static ++Color InspectorDOMAgent::parseColor(const JSON::Object* colorObject) + { + if (!colorObject) + return Color::transparent; +@@ -157,7 +164,7 @@ static Color parseConfigColor(const String& fieldName, const JSON::Object* confi + RefPtr colorObject; + configObject->getObject(fieldName, colorObject); + +- return parseColor(colorObject.get()); ++ return InspectorDOMAgent::parseColor(colorObject.get()); + } + + static bool parseQuad(const JSON::Array& quadArray, FloatQuad* quad) +@@ -1475,6 +1482,61 @@ void InspectorDOMAgent::setInspectedNode(ErrorString& errorString, int nodeId) m_suppressEventListenerChangedEvent = false; } @@ -1290,7 +1318,7 @@ index aecc79bc0ca..57ce50c1f94 100644 void InspectorDOMAgent::resolveNode(ErrorString& errorString, int nodeId, const String* objectGroup, RefPtr& result) { String objectGroupName = objectGroup ? *objectGroup : emptyString(); -@@ -2686,4 +2747,46 @@ void InspectorDOMAgent::setAllowEditingUserAgentShadowTrees(ErrorString&, bool a +@@ -2686,4 +2748,46 @@ void InspectorDOMAgent::setAllowEditingUserAgentShadowTrees(ErrorString&, bool a m_allowEditingUserAgentShadowTrees = allow; } @@ -1338,10 +1366,26 @@ index aecc79bc0ca..57ce50c1f94 100644 + } // namespace WebCore diff --git a/Source/WebCore/inspector/agents/InspectorDOMAgent.h b/Source/WebCore/inspector/agents/InspectorDOMAgent.h -index 51639abeb84..16080f2c017 100644 +index 51639abeb84..0ed9a1d80d5 100644 --- a/Source/WebCore/inspector/agents/InspectorDOMAgent.h +++ b/Source/WebCore/inspector/agents/InspectorDOMAgent.h -@@ -148,6 +148,8 @@ public: +@@ -54,6 +54,7 @@ namespace WebCore { + + class AXCoreObject; + class CharacterData; ++class Color; + class DOMEditor; + class Document; + class Element; +@@ -88,6 +89,7 @@ public: + static String toErrorString(Exception&&); + + static String documentURLString(Document*); ++ static Color parseColor(const JSON::Object*); + + // We represent embedded doms as a part of the same hierarchy. Hence we treat children of frame owners differently. + // We also skip whitespace text nodes conditionally. Following methods encapsulate these specifics. +@@ -148,6 +150,8 @@ public: void focus(ErrorString&, int nodeId) override; void setInspectedNode(ErrorString&, int nodeId) override; void setAllowEditingUserAgentShadowTrees(ErrorString&, bool allow) final; @@ -1350,8 +1394,20 @@ index 51639abeb84..16080f2c017 100644 // InspectorInstrumentation int identifierForNode(Node&); +diff --git a/Source/WebCore/inspector/agents/InspectorDOMStorageAgent.h b/Source/WebCore/inspector/agents/InspectorDOMStorageAgent.h +index b578660fbb3..a7c968bc9f8 100644 +--- a/Source/WebCore/inspector/agents/InspectorDOMStorageAgent.h ++++ b/Source/WebCore/inspector/agents/InspectorDOMStorageAgent.h +@@ -40,6 +40,7 @@ class DOMStorageFrontendDispatcher; + + namespace WebCore { + ++class Color; + class Frame; + class Page; + class SecurityOrigin; diff --git a/Source/WebCore/inspector/agents/InspectorPageAgent.cpp b/Source/WebCore/inspector/agents/InspectorPageAgent.cpp -index f2e228b7f74..1e6ef4eec98 100644 +index f2e228b7f74..f31341803b7 100644 --- a/Source/WebCore/inspector/agents/InspectorPageAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorPageAgent.cpp @@ -32,6 +32,8 @@ @@ -1484,7 +1540,7 @@ index f2e228b7f74..1e6ef4eec98 100644 Ref InspectorPageAgent::buildObjectForFrame(Frame* frame) { ASSERT_ARG(frame, frame); -@@ -986,4 +1034,455 @@ void InspectorPageAgent::archive(ErrorString& errorString, String* data) +@@ -986,4 +1034,469 @@ void InspectorPageAgent::archive(ErrorString& errorString, String* data) #endif } @@ -1938,10 +1994,24 @@ index f2e228b7f74..1e6ef4eec98 100644 +void InspectorPageAgent::setInterceptFileChooserDialog(ErrorString&, bool enabled) { + m_interceptFileChooserDialog = enabled; +} ++ ++void InspectorPageAgent::setDefaultBackgroundColorOverride(ErrorString& errorString, const JSON::Object* color) ++{ ++ FrameView* view = m_inspectedPage.mainFrame().view(); ++ if (!view) { ++ errorString = "Internal error: No frame view to set color two"_s; ++ return; ++ } ++ if (!color) { ++ view->updateBackgroundRecursively(Optional()); ++ return; ++ } ++ view->updateBackgroundRecursively(InspectorDOMAgent::parseColor(color)); ++} + } // namespace WebCore diff --git a/Source/WebCore/inspector/agents/InspectorPageAgent.h b/Source/WebCore/inspector/agents/InspectorPageAgent.h -index 4fd8c0b1016..eb18b0fc48e 100644 +index 4fd8c0b1016..1c12c0a4e54 100644 --- a/Source/WebCore/inspector/agents/InspectorPageAgent.h +++ b/Source/WebCore/inspector/agents/InspectorPageAgent.h @@ -40,10 +40,15 @@ @@ -1971,7 +2041,7 @@ index 4fd8c0b1016..eb18b0fc48e 100644 void overrideUserAgent(ErrorString&, const String* value) override; void overrideSetting(ErrorString&, const String& setting, const bool* value) override; void getCookies(ErrorString&, RefPtr>& cookies) override; -@@ -113,8 +120,11 @@ public: +@@ -113,8 +120,12 @@ public: void getCompositingBordersVisible(ErrorString&, bool* out_param) override; void setCompositingBordersVisible(ErrorString&, bool) override; void snapshotNode(ErrorString&, int nodeId, String* outDataURL) override; @@ -1981,10 +2051,11 @@ index 4fd8c0b1016..eb18b0fc48e 100644 + void insertText(ErrorString&, const String& text) override; + void accessibilitySnapshot(ErrorString&, RefPtr& out_axNode) override; + void setInterceptFileChooserDialog(ErrorString&, bool enabled) override; ++ void setDefaultBackgroundColorOverride(ErrorString&, const JSON::Object*) override; // InspectorInstrumentation void domContentEventFired(); -@@ -126,6 +136,7 @@ public: +@@ -126,6 +137,7 @@ public: void frameStoppedLoading(Frame&); void frameScheduledNavigation(Frame&, Seconds delay); void frameClearedScheduledNavigation(Frame&); @@ -1992,7 +2063,7 @@ index 4fd8c0b1016..eb18b0fc48e 100644 void defaultAppearanceDidChange(bool useDarkAppearance); void applyUserAgentOverride(String&); void applyEmulatedMedia(String&); -@@ -134,6 +145,7 @@ public: +@@ -134,6 +146,7 @@ public: void didLayout(); void didScroll(); void didRecalculateStyle(); @@ -2000,7 +2071,7 @@ index 4fd8c0b1016..eb18b0fc48e 100644 Frame* frameForId(const String& frameId); WEBCORE_EXPORT String frameId(Frame*); -@@ -153,6 +165,7 @@ private: +@@ -153,6 +166,7 @@ private: RefPtr m_backendDispatcher; Page& m_inspectedPage; @@ -2008,7 +2079,7 @@ index 4fd8c0b1016..eb18b0fc48e 100644 InspectorClient* m_client { nullptr }; InspectorOverlay* m_overlay { nullptr }; -@@ -165,6 +178,7 @@ private: +@@ -165,6 +179,7 @@ private: String m_bootstrapScript; bool m_isFirstLayoutAfterOnLoad { false }; bool m_showPaintRects { false }; @@ -4892,10 +4963,10 @@ index 78caedf0c0c..40f08285590 100644 void setIndicating(bool); diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp new file mode 100644 -index 00000000000..e903413c95d +index 00000000000..f10c1651e64 --- /dev/null +++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp -@@ -0,0 +1,47 @@ +@@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + @@ -4930,9 +5001,10 @@ index 00000000000..e903413c95d +{ +} + -+void WebPageInspectorEmulationAgent::setDeviceMetricsOverride(ErrorString& error, int in_width, int in_height) ++void WebPageInspectorEmulationAgent::setDeviceMetricsOverride(ErrorString& error, int in_width, int in_height, double in_deviceScaleFactor) +{ + platformSetSize(error, in_width, in_height); ++ m_page.setCustomDeviceScaleFactor(in_deviceScaleFactor); +} + +void WebPageInspectorEmulationAgent::setJavaScriptEnabled(ErrorString&, bool enabled) @@ -4945,7 +5017,7 @@ index 00000000000..e903413c95d +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h new file mode 100644 -index 00000000000..b02753590b3 +index 00000000000..0025b0be853 --- /dev/null +++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h @@ -0,0 +1,42 @@ @@ -4980,7 +5052,7 @@ index 00000000000..b02753590b3 + void didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*) override; + void willDestroyFrontendAndBackend(Inspector::DisconnectReason) override; + -+ void setDeviceMetricsOverride(Inspector::ErrorString&, int in_width, int in_height) override; ++ void setDeviceMetricsOverride(Inspector::ErrorString&, int in_width, int in_height, double in_deviceScaleFactor) override; + void setJavaScriptEnabled(Inspector::ErrorString&, bool enabled) override; + +private: @@ -6095,15 +6167,16 @@ index 00000000000..7ce9b71b0fb +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/mac/WebPageInspectorTargetProxyMac.mm b/Source/WebKit/UIProcess/mac/WebPageInspectorTargetProxyMac.mm new file mode 100644 -index 00000000000..06a7e286abf +index 00000000000..2061f6d129b --- /dev/null +++ b/Source/WebKit/UIProcess/mac/WebPageInspectorTargetProxyMac.mm -@@ -0,0 +1,18 @@ +@@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + -+#include "config.h" -+#include "WebPageInspectorTargetProxy.h" ++#import "config.h" ++#import "WebPageInspectorTargetProxy.h" ++#import "WebPageProxy.h" + +#if PLATFORM(MAC) + @@ -6111,7 +6184,8 @@ index 00000000000..06a7e286abf + +void WebPageInspectorTargetProxy::platformActivate(String& error) const +{ -+ error = "Not Implemented"; ++ NSWindow* window = m_page.platformWindow(); ++ [window makeKeyAndOrderFront:nil]; +} + +} // namespace WebKit @@ -7094,5 +7168,5 @@ index 2d183d39412..d94d4f06fc5 100644 webkit_web_context_set_tls_errors_policy(webContext, WEBKIT_TLS_ERRORS_POLICY_IGNORE); -- -2.22.1 +2.24.0 From e992c7fa7d77c0c96b7aa347c4e72bab0f1441ae Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 5 Dec 2019 14:48:39 -0800 Subject: [PATCH 06/42] chore: unify screenshot handling between browsers, introduce Screenshotter everywhere (#156) --- docs/api.md | 11 +++++ src/chromium/JSHandle.ts | 3 +- src/chromium/Page.ts | 4 +- src/chromium/Screenshotter.ts | 59 +++--------------------- src/dom.ts | 4 +- src/firefox/JSHandle.ts | 22 ++------- src/firefox/Page.ts | 48 +++----------------- src/firefox/Screenshotter.ts | 64 ++++++++++++++++++++++++++ src/helper.ts | 40 +++++++++++++++++ src/types.ts | 10 +++++ src/webkit/Browser.ts | 6 +-- src/webkit/JSHandle.ts | 18 ++------ src/webkit/Page.ts | 85 ++++------------------------------- src/webkit/Screenshotter.ts | 83 ++++++++++++++++++++++++++++++++++ src/webkit/Target.ts | 2 +- src/webkit/TaskQueue.ts | 30 ------------- 16 files changed, 243 insertions(+), 246 deletions(-) create mode 100644 src/firefox/Screenshotter.ts create mode 100644 src/webkit/Screenshotter.ts delete mode 100644 src/webkit/TaskQueue.ts diff --git a/docs/api.md b/docs/api.md index afe0318acd..985568c8c8 100644 --- a/docs/api.md +++ b/docs/api.md @@ -3457,6 +3457,17 @@ If `key` is a single character and no modifier keys besides `Shift` are being he #### elementHandle.screenshot([options]) - `options` <[Object]> Same options as in [page.screenshot](#pagescreenshotoptions). + - `path` <[string]> The file path to save the image to. The screenshot type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). If no path is provided, the image won't be saved to the disk. + - `type` <"png"|"jpeg"> Specify screenshot type, defaults to 'png'. + - `quality` <[number]> The quality of the image, between 0-100. Not applicable to `png` images. + - `fullPage` <[boolean]> When true, takes a screenshot of the full scrollable page. Defaults to `false`. + - `clip` <[Object]> Passed clip value is ignored and instead set to the element's bounding box. + - `x` <[number]> + - `y` <[number]> + - `width` <[number]> + - `height` <[number]> + - `omitBackground` <[boolean]> Hides default white background and allows capturing screenshots with transparency. Defaults to `false`. + - `encoding` <[string]> The encoding of the image, can be either `base64` or `binary`. Defaults to `binary`. - returns: <[Promise]<[string]|[Buffer]>> Promise which resolves to buffer or a base64 string (depending on the value of `options.encoding`) with captured screenshot. This method scrolls element into view if needed, and then uses [page.screenshot](#pagescreenshotoptions) to take a screenshot of the element. diff --git a/src/chromium/JSHandle.ts b/src/chromium/JSHandle.ts index b6434f8f50..8163f5c3f4 100644 --- a/src/chromium/JSHandle.ts +++ b/src/chromium/JSHandle.ts @@ -23,7 +23,6 @@ import * as frames from '../frames'; import { CDPSession } from './Connection'; import { FrameManager } from './FrameManager'; import { Protocol } from './protocol'; -import { ScreenshotOptions } from './Screenshotter'; import { ExecutionContextDelegate } from './ExecutionContext'; export class DOMWorldDelegate implements dom.DOMWorldDelegate { @@ -91,7 +90,7 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate { return { width: layoutMetrics.layoutViewport.clientWidth, height: layoutMetrics.layoutViewport.clientHeight }; } - screenshot(handle: dom.ElementHandle, options: ScreenshotOptions = {}): Promise { + screenshot(handle: dom.ElementHandle, options?: types.ScreenshotOptions): Promise { const page = this._frameManager.page(); return page._screenshotter.screenshotElement(page, handle, options); } diff --git a/src/chromium/Page.ts b/src/chromium/Page.ts index ae93268ab5..a9a68ad8d6 100644 --- a/src/chromium/Page.ts +++ b/src/chromium/Page.ts @@ -44,7 +44,7 @@ import * as network from '../network'; import * as dialog from '../dialog'; import * as console from '../console'; import { DOMWorldDelegate } from './JSHandle'; -import { Screenshotter, ScreenshotOptions } from './Screenshotter'; +import { Screenshotter } from './Screenshotter'; export type Viewport = { width: number; @@ -509,7 +509,7 @@ export class Page extends EventEmitter { await this._frameManager.networkManager().setCacheEnabled(enabled); } - screenshot(options: ScreenshotOptions = {}): Promise { + screenshot(options?: types.ScreenshotOptions): Promise { return this._screenshotter.screenshotPage(this, options); } diff --git a/src/chromium/Screenshotter.ts b/src/chromium/Screenshotter.ts index 2740233480..154fbd1605 100644 --- a/src/chromium/Screenshotter.ts +++ b/src/chromium/Screenshotter.ts @@ -18,32 +18,22 @@ import * as fs from 'fs'; import { Page } from './Page'; import { assert, helper } from '../helper'; -import * as mime from 'mime'; import { Protocol } from './protocol'; import * as dom from '../dom'; +import * as types from '../types'; const writeFileAsync = helper.promisify(fs.writeFile); -export type ScreenshotOptions = { - type?: 'png' | 'jpeg', - path?: string, - fullPage?: boolean, - clip?: {x: number, y: number, width: number, height: number}, - quality?: number, - omitBackground?: boolean, - encoding?: string, -} - export class Screenshotter { private _queue = new TaskQueue(); - async screenshotPage(page: Page, options: ScreenshotOptions = {}): Promise { - const format = this._format(options); + async screenshotPage(page: Page, options: types.ScreenshotOptions = {}): Promise { + const format = helper.validateScreeshotOptions(options); return this._queue.postTask(() => this._screenshot(page, format, options)); } - async screenshotElement(page: Page, handle: dom.ElementHandle, options: ScreenshotOptions = {}): Promise { - const format = this._format(options); + async screenshotElement(page: Page, handle: dom.ElementHandle, options: types.ScreenshotOptions = {}): Promise { + const format = helper.validateScreeshotOptions(options); return this._queue.postTask(async () => { let needsViewportReset = false; @@ -84,7 +74,7 @@ export class Screenshotter { }); } - private async _screenshot(page: Page, format: 'png' | 'jpeg', options: ScreenshotOptions): Promise { + private async _screenshot(page: Page, format: 'png' | 'jpeg', options: types.ScreenshotOptions): Promise { await page.browser()._activatePage(page); let clip = options.clip ? processClip(options.clip) : undefined; const viewport = page.viewport(); @@ -127,43 +117,6 @@ export class Screenshotter { return {x, y, width, height, scale: 1}; } } - - private _format(options: ScreenshotOptions): 'png' | 'jpeg' { - let format: 'png' | 'jpeg' | null = null; - // options.type takes precedence over inferring the type from options.path - // because it may be a 0-length file with no extension created beforehand (i.e. as a temp file). - if (options.type) { - assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type); - format = options.type; - } else if (options.path) { - const mimeType = mime.getType(options.path); - if (mimeType === 'image/png') - format = 'png'; - else if (mimeType === 'image/jpeg') - format = 'jpeg'; - assert(format, 'Unsupported screenshot mime type: ' + mimeType); - } - - if (!format) - format = 'png'; - - if (options.quality) { - assert(format === 'jpeg', 'options.quality is unsupported for the ' + format + ' screenshots'); - assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + (typeof options.quality)); - assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer'); - assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality); - } - assert(!options.clip || !options.fullPage, 'options.clip and options.fullPage are exclusive'); - if (options.clip) { - assert(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' + (typeof options.clip.x)); - assert(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' + (typeof options.clip.y)); - assert(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' + (typeof options.clip.width)); - assert(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' + (typeof options.clip.height)); - assert(options.clip.width !== 0, 'Expected options.clip.width not to be 0.'); - assert(options.clip.height !== 0, 'Expected options.clip.height not to be 0.'); - } - return format; - } } class TaskQueue { diff --git a/src/dom.ts b/src/dom.ts index d26248e649..458ecb53c0 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -22,7 +22,7 @@ export interface DOMWorldDelegate { contentQuads(handle: ElementHandle): Promise; layoutViewport(): Promise<{ width: number, height: number }>; boundingBox(handle: ElementHandle): Promise; - screenshot(handle: ElementHandle, options?: any): Promise; + screenshot(handle: ElementHandle, options?: types.ScreenshotOptions): Promise; setInputFiles(handle: ElementHandle, files: input.FilePayload[]): Promise; adoptElementHandle(handle: ElementHandle, to: DOMWorld): Promise; } @@ -359,7 +359,7 @@ export class ElementHandle extends js.JSHandle { return this._world.delegate.boundingBox(this); } - async screenshot(options: any = {}): Promise { + async screenshot(options?: types.ScreenshotOptions): Promise { return this._world.delegate.screenshot(this, options); } diff --git a/src/firefox/JSHandle.ts b/src/firefox/JSHandle.ts index 3a6843a101..c3e6f10fcc 100644 --- a/src/firefox/JSHandle.ts +++ b/src/firefox/JSHandle.ts @@ -92,25 +92,9 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate { return this._frameManager._page.evaluate(() => ({ width: innerWidth, height: innerHeight })); } - async screenshot(handle: dom.ElementHandle, options: any = {}): Promise { - const clip = await this._session.send('Page.getBoundingBox', { - frameId: this._frameId, - objectId: toRemoteObject(handle).objectId, - }); - if (!clip) - throw new Error('Node is either not visible or not an HTMLElement'); - assert(clip.width, 'Node has 0 width.'); - assert(clip.height, 'Node has 0 height.'); - await handle._scrollIntoViewIfNeeded(); - - return await this._frameManager._page.screenshot(Object.assign({}, options, { - clip: { - x: clip.x, - y: clip.y, - width: clip.width, - height: clip.height, - }, - })); + async screenshot(handle: dom.ElementHandle, options?: types.ScreenshotOptions): Promise { + const page = this._frameManager._page; + return page._screenshotter.screenshotElement(page, handle, options); } async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise { diff --git a/src/firefox/Page.ts b/src/firefox/Page.ts index 7ffd457c38..44b439f42e 100644 --- a/src/firefox/Page.ts +++ b/src/firefox/Page.ts @@ -16,8 +16,6 @@ */ import { EventEmitter } from 'events'; -import * as fs from 'fs'; -import * as mime from 'mime'; import { TimeoutError } from '../Errors'; import { assert, debugError, helper, RegisteredListener } from '../helper'; import { TimeoutSettings } from '../TimeoutSettings'; @@ -38,8 +36,7 @@ import * as network from '../network'; import * as frames from '../frames'; import * as dialog from '../dialog'; import * as console from '../console'; - -const writeFileAsync = helper.promisify(fs.writeFile); +import { Screenshotter } from './Screenshotter'; export class Page extends EventEmitter { private _timeoutSettings: TimeoutSettings; @@ -60,6 +57,7 @@ export class Page extends EventEmitter { private _viewport: Viewport; private _disconnectPromise: Promise; private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>(); + _screenshotter: Screenshotter; static async create(session: JugglerSession, browserContext: BrowserContext, defaultViewport: Viewport | null) { const page = new Page(session, browserContext); @@ -107,6 +105,7 @@ export class Page extends EventEmitter { helper.addEventListener(this._networkManager, NetworkManagerEvents.RequestFailed, request => this.emit(Events.Page.RequestFailed, request)), ]; this._viewport = null; + this._screenshotter = new Screenshotter(session); } _didClose() { @@ -425,26 +424,8 @@ export class Page extends EventEmitter { return watchDog.navigationResponse(); } - async screenshot(options: { fullPage?: boolean; clip?: { width: number; height: number; x: number; y: number; }; encoding?: string; path?: string; } = {}): Promise { - const {data} = await this._session.send('Page.screenshot', { - mimeType: getScreenshotMimeType(options), - fullPage: options.fullPage, - clip: processClip(options.clip), - }); - const buffer = options.encoding === 'base64' ? data : Buffer.from(data, 'base64'); - if (options.path) - await writeFileAsync(options.path, buffer); - return buffer; - - function processClip(clip) { - if (!clip) - return undefined; - const x = Math.round(clip.x); - const y = Math.round(clip.y); - const width = Math.round(clip.width + clip.x - x); - const height = Math.round(clip.height + clip.y - y); - return {x, y, width, height}; - } + screenshot(options?: types.ScreenshotOptions): Promise { + return this._screenshotter.screenshotPage(this, options); } evaluate: types.Evaluate = (pageFunction, ...args) => { @@ -589,25 +570,6 @@ export class Page extends EventEmitter { } } -function getScreenshotMimeType(options) { - // options.type takes precedence over inferring the type from options.path - // because it may be a 0-length file with no extension created beforehand (i.e. as a temp file). - if (options.type) { - if (options.type === 'png') - return 'image/png'; - if (options.type === 'jpeg') - return 'image/jpeg'; - throw new Error('Unknown options.type value: ' + options.type); - } - if (options.path) { - const fileType = mime.getType(options.path); - if (fileType === 'image/png' || fileType === 'image/jpeg') - return fileType; - throw new Error('Unsupported screenshot mime type: ' + fileType); - } - return 'image/png'; -} - export type Viewport = { width: number; height: number; diff --git a/src/firefox/Screenshotter.ts b/src/firefox/Screenshotter.ts new file mode 100644 index 0000000000..3768c3972f --- /dev/null +++ b/src/firefox/Screenshotter.ts @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import * as fs from 'fs'; +import { Page } from './Page'; +import { assert, helper } from '../helper'; +import * as dom from '../dom'; +import * as types from '../types'; +import { JugglerSession } from './Connection'; + +const writeFileAsync = helper.promisify(fs.writeFile); + +export class Screenshotter { + private _session: JugglerSession; + + constructor(session: JugglerSession) { + this._session = session; + } + + async screenshotPage(page: Page, options: types.ScreenshotOptions = {}): Promise { + const format = helper.validateScreeshotOptions(options); + const {data} = await this._session.send('Page.screenshot', { + mimeType: ('image/' + format) as ('image/png' | 'image/jpeg'), + fullPage: options.fullPage, + clip: processClip(options.clip), + }); + const buffer = options.encoding === 'base64' ? data : Buffer.from(data, 'base64'); + if (options.path) + await writeFileAsync(options.path, buffer); + return buffer; + + function processClip(clip) { + if (!clip) + return undefined; + const x = Math.round(clip.x); + const y = Math.round(clip.y); + const width = Math.round(clip.width + clip.x - x); + const height = Math.round(clip.height + clip.y - y); + return {x, y, width, height}; + } + } + + async screenshotElement(page: Page, handle: dom.ElementHandle, options: types.ScreenshotOptions = {}): Promise { + const frameId = page._frameManager._frameData(handle.executionContext().frame()).frameId; + const clip = await this._session.send('Page.getBoundingBox', { + frameId, + objectId: handle._remoteObject.objectId, + }); + if (!clip) + throw new Error('Node is either not visible or not an HTMLElement'); + assert(clip.width, 'Node has 0 width.'); + assert(clip.height, 'Node has 0 height.'); + await handle._scrollIntoViewIfNeeded(); + return this.screenshotPage(page, { + ...options, + clip: { + x: clip.x, + y: clip.y, + width: clip.width, + height: clip.height, + }, + }); + } +} diff --git a/src/helper.ts b/src/helper.ts index ef56e325da..dc560c1318 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -14,8 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import * as debug from 'debug'; +import * as mime from 'mime'; import { TimeoutError } from './Errors'; +import * as types from './types'; export const debugError = debug(`playwright:error`); @@ -152,6 +155,43 @@ class Helper { clearTimeout(timeoutTimer); } } + + static validateScreeshotOptions(options: types.ScreenshotOptions): 'png' | 'jpeg' { + let format: 'png' | 'jpeg' | null = null; + // options.type takes precedence over inferring the type from options.path + // because it may be a 0-length file with no extension created beforehand (i.e. as a temp file). + if (options.type) { + assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type); + format = options.type; + } else if (options.path) { + const mimeType = mime.getType(options.path); + if (mimeType === 'image/png') + format = 'png'; + else if (mimeType === 'image/jpeg') + format = 'jpeg'; + assert(format, 'Unsupported screenshot mime type: ' + mimeType); + } + + if (!format) + format = 'png'; + + if (options.quality) { + assert(format === 'jpeg', 'options.quality is unsupported for the ' + format + ' screenshots'); + assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + (typeof options.quality)); + assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer'); + assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality); + } + assert(!options.clip || !options.fullPage, 'options.clip and options.fullPage are exclusive'); + if (options.clip) { + assert(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' + (typeof options.clip.x)); + assert(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' + (typeof options.clip.y)); + assert(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' + (typeof options.clip.width)); + assert(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' + (typeof options.clip.height)); + assert(options.clip.width !== 0, 'Expected options.clip.width not to be 0.'); + assert(options.clip.height !== 0, 'Expected options.clip.height not to be 0.'); + } + return format; + } } export function assert(value: any, message?: string) { diff --git a/src/types.ts b/src/types.ts index 96e31ada28..c6a968c46a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -38,3 +38,13 @@ export function clearSelector(selector: string | Selector): string | Selector { return selector; return { selector: selector.selector, visible: selector.visible }; } + +export type ScreenshotOptions = { + type?: 'png' | 'jpeg', + path?: string, + fullPage?: boolean, + clip?: Rect, + quality?: number, + omitBackground?: boolean, + encoding?: string, +}; diff --git a/src/webkit/Browser.ts b/src/webkit/Browser.ts index e54010b9aa..3fc05b20d6 100644 --- a/src/webkit/Browser.ts +++ b/src/webkit/Browser.ts @@ -22,13 +22,13 @@ import { filterCookies, NetworkCookie, rewriteCookies, SetNetworkCookieParam } f import { Connection } from './Connection'; import { Page, Viewport } from './Page'; import { Target } from './Target'; -import { TaskQueue } from './TaskQueue'; +import { Screenshotter } from './Screenshotter'; import { Protocol } from './protocol'; export class Browser extends EventEmitter { _defaultViewport: Viewport; private _process: childProcess.ChildProcess; - _screenshotTaskQueue = new TaskQueue(); + _screenshotter = new Screenshotter(); _connection: Connection; private _closeCallback: () => Promise; private _defaultContext: BrowserContext; @@ -62,7 +62,7 @@ export class Browser extends EventEmitter { ]; // Taking multiple screenshots in parallel doesn't work well, so we serialize them. - this._screenshotTaskQueue = new TaskQueue(); + this._screenshotter = new Screenshotter(); } async userAgent(): Promise { diff --git a/src/webkit/JSHandle.ts b/src/webkit/JSHandle.ts index b3a363a0d0..a4e26cd086 100644 --- a/src/webkit/JSHandle.ts +++ b/src/webkit/JSHandle.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import * as fs from 'fs'; -import { debugError, helper, assert } from '../helper'; +import { debugError, assert } from '../helper'; import * as input from '../input'; import * as dom from '../dom'; import * as frames from '../frames'; @@ -25,8 +24,6 @@ import { TargetSession } from './Connection'; import { FrameManager } from './FrameManager'; import { Protocol } from './protocol'; -const writeFileAsync = helper.promisify(fs.writeFile); - export class DOMWorldDelegate implements dom.DOMWorldDelegate { readonly keyboard: input.Keyboard; readonly mouse: input.Mouse; @@ -91,16 +88,9 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate { return this._frameManager._page.evaluate(() => ({ width: innerWidth, height: innerHeight })); } - async screenshot(handle: dom.ElementHandle, options: any = {}): Promise { - const objectId = toRemoteObject(handle).objectId; - this._client.send('DOM.getDocument'); - const {nodeId} = await this._client.send('DOM.requestNode', {objectId}); - const result = await this._client.send('Page.snapshotNode', {nodeId}); - const prefix = 'data:image/png;base64,'; - const buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64'); - if (options.path) - await writeFileAsync(options.path, buffer); - return buffer; + screenshot(handle: dom.ElementHandle, options?: types.ScreenshotOptions): Promise { + const page = this._frameManager._page; + return page._screenshotter.screenshotElement(page, handle, options); } async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise { diff --git a/src/webkit/Page.ts b/src/webkit/Page.ts index 266baaebf5..0f391f5571 100644 --- a/src/webkit/Page.ts +++ b/src/webkit/Page.ts @@ -16,8 +16,6 @@ */ import { EventEmitter } from 'events'; -import * as fs from 'fs'; -import * as mime from 'mime'; import { assert, debugError, helper, RegisteredListener } from '../helper'; import { ClickOptions, mediaColorSchemes, mediaTypes, MultiClickOptions } from '../input'; import { TimeoutSettings } from '../TimeoutSettings'; @@ -28,7 +26,7 @@ import { FrameManager, FrameManagerEvents } from './FrameManager'; import { RawKeyboardImpl, RawMouseImpl } from './Input'; import { NetworkManagerEvents } from './NetworkManager'; import { Protocol } from './protocol'; -import { TaskQueue } from './TaskQueue'; +import { Screenshotter } from './Screenshotter'; import * as input from '../input'; import * as types from '../types'; import * as frames from '../frames'; @@ -38,8 +36,6 @@ import * as network from '../network'; import * as dialog from '../dialog'; import * as console from '../console'; -const writeFileAsync = helper.promisify(fs.writeFile); - export type Viewport = { width: number; height: number; @@ -58,22 +54,22 @@ export class Page extends EventEmitter { private _bootstrapScripts: string[] = []; _javascriptEnabled = true; private _viewport: Viewport | null = null; - private _screenshotTaskQueue: TaskQueue; + _screenshotter: Screenshotter; private _workers = new Map(); private _disconnectPromise: Promise | undefined; private _sessionListeners: RegisteredListener[] = []; private _emulatedMediaType: string | undefined; private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>(); - static async create(session: TargetSession, browserContext: BrowserContext, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue): Promise { - const page = new Page(session, browserContext, screenshotTaskQueue); + static async create(session: TargetSession, browserContext: BrowserContext, defaultViewport: Viewport | null, screenshotter: Screenshotter): Promise { + const page = new Page(session, browserContext, screenshotter); await page._initialize(); if (defaultViewport) await page.setViewport(defaultViewport); return page; } - constructor(session: TargetSession, browserContext: BrowserContext, screenshotTaskQueue: TaskQueue) { + constructor(session: TargetSession, browserContext: BrowserContext, screenshotter: Screenshotter) { super(); this._closedPromise = new Promise(f => this._closedCallback = f); this._keyboard = new input.Keyboard(new RawKeyboardImpl(session)); @@ -81,7 +77,7 @@ export class Page extends EventEmitter { this._timeoutSettings = new TimeoutSettings(); this._frameManager = new FrameManager(session, this, this._timeoutSettings); - this._screenshotTaskQueue = screenshotTaskQueue; + this._screenshotter = screenshotter; this._setSession(session); this._browserContext = browserContext; @@ -371,63 +367,8 @@ export class Page extends EventEmitter { await this._frameManager.networkManager().setCacheEnabled(enabled); } - async screenshot(options: ScreenshotOptions = {}): Promise { - let screenshotType = null; - // options.type takes precedence over inferring the type from options.path - // because it may be a 0-length file with no extension created beforehand (i.e. as a temp file). - if (options.type) { - assert(options.type === 'png', 'Unknown options.type value: ' + options.type); - screenshotType = options.type; - } else if (options.path) { - const mimeType = mime.getType(options.path); - if (mimeType === 'image/png') - screenshotType = 'png'; - assert(screenshotType, 'Unsupported screenshot mime type: ' + mimeType); - } - - if (!screenshotType) - screenshotType = 'png'; - - if (options.quality) - assert(screenshotType === 'jpeg', 'options.quality is unsupported for the ' + screenshotType + ' screenshots'); - assert(!options.clip || !options.fullPage, 'options.clip and options.fullPage are exclusive'); - if (options.clip) { - assert(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' + (typeof options.clip.x)); - assert(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' + (typeof options.clip.y)); - assert(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' + (typeof options.clip.width)); - assert(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' + (typeof options.clip.height)); - assert(options.clip.width !== 0, 'Expected options.clip.width not to be 0.'); - assert(options.clip.height !== 0, 'Expected options.clip.height not to be 0.'); - } - return this._screenshotTaskQueue.postTask(this._screenshotTask.bind(this, options)); - } - - async _screenshotTask(options?: ScreenshotOptions): Promise { - const params: Protocol.Page.snapshotRectParameters = { x: 0, y: 0, width: 800, height: 600, coordinateSystem: 'Page' }; - if (options.fullPage) { - const pageSize = await this.evaluate(() => - ({ - width: document.body.scrollWidth, - height: document.body.scrollHeight - })); - Object.assign(params, pageSize); - } else if (options.clip) { - Object.assign(params, options.clip); - } else if (this._viewport) { - Object.assign(params, this._viewport); - } - const [, result] = await Promise.all([ - this.browser()._activatePage(this), - this._session.send('Page.snapshotRect', params), - ]).catch(e => { - debugError('Failed to take screenshot: ' + e); - throw e; - }); - const prefix = 'data:image/png;base64,'; - const buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64'); - if (options.path) - await writeFileAsync(options.path, buffer); - return buffer; + screenshot(options?: types.ScreenshotOptions): Promise { + return this._screenshotter.screenshotPage(this, options); } async title(): Promise { @@ -540,16 +481,6 @@ type Metrics = { JSHeapTotalSize?: number, } -type ScreenshotOptions = { - type?: string, - path?: string, - fullPage?: boolean, - clip?: {x: number, y: number, width: number, height: number}, - quality?: number, - omitBackground?: boolean, - encoding?: string, -} - type FileChooser = { element: dom.ElementHandle, multiple: boolean diff --git a/src/webkit/Screenshotter.ts b/src/webkit/Screenshotter.ts new file mode 100644 index 0000000000..737e9f5113 --- /dev/null +++ b/src/webkit/Screenshotter.ts @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import * as fs from 'fs'; +import { Page } from './Page'; +import { assert, helper, debugError } from '../helper'; +import { Protocol } from './protocol'; +import * as dom from '../dom'; +import * as types from '../types'; + +const writeFileAsync = helper.promisify(fs.writeFile); + +export class Screenshotter { + private _queue = new TaskQueue(); + + async screenshotPage(page: Page, options: types.ScreenshotOptions = {}): Promise { + const format = helper.validateScreeshotOptions(options); + assert(format === 'png', 'Only png format is supported'); + return this._queue.postTask(async () => { + const params: Protocol.Page.snapshotRectParameters = { x: 0, y: 0, width: 800, height: 600, coordinateSystem: 'Page' }; + if (options.fullPage) { + const pageSize = await page.evaluate(() => + ({ + width: document.body.scrollWidth, + height: document.body.scrollHeight + })); + Object.assign(params, pageSize); + } else if (options.clip) { + Object.assign(params, options.clip); + } else if (page.viewport()) { + Object.assign(params, page.viewport()); + } + const [, result] = await Promise.all([ + page.browser()._activatePage(page), + page._session.send('Page.snapshotRect', params), + ]).catch(e => { + debugError('Failed to take screenshot: ' + e); + throw e; + }); + const prefix = 'data:image/png;base64,'; + const buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64'); + if (options.path) + await writeFileAsync(options.path, buffer); + return buffer; + }); + } + + async screenshotElement(page: Page, handle: dom.ElementHandle, options: types.ScreenshotOptions = {}): Promise { + const format = helper.validateScreeshotOptions(options); + assert(format === 'png', 'Only png format is supported'); + return this._queue.postTask(async () => { + const objectId = (handle._remoteObject as Protocol.Runtime.RemoteObject).objectId; + page._session.send('DOM.getDocument'); + const {nodeId} = await page._session.send('DOM.requestNode', {objectId}); + const [, result] = await Promise.all([ + page.browser()._activatePage(page), + page._session.send('Page.snapshotNode', {nodeId}) + ]).catch(e => { + debugError('Failed to take screenshot: ' + e); + throw e; + }); + const prefix = 'data:image/png;base64,'; + const buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64'); + if (options.path) + await writeFileAsync(options.path, buffer); + return buffer; + }); + } +} + +class TaskQueue { + private _chain: Promise; + + constructor() { + this._chain = Promise.resolve(); + } + + postTask(task: () => any): Promise { + const result = this._chain.then(task); + this._chain = result.catch(() => {}); + return result; + } +} diff --git a/src/webkit/Target.ts b/src/webkit/Target.ts index 785fe6119c..481f3d8284 100644 --- a/src/webkit/Target.ts +++ b/src/webkit/Target.ts @@ -59,7 +59,7 @@ export class Target { async page(): Promise { if (this._type === 'page' && !this._pagePromise) { const session = this.browser()._connection.session(this._targetId); - this._pagePromise = Page.create(session, this._browserContext, this.browser()._defaultViewport, this.browser()._screenshotTaskQueue).then(page => { + this._pagePromise = Page.create(session, this._browserContext, this.browser()._defaultViewport, this.browser()._screenshotter).then(page => { this._adoptPage(page); return page; }); diff --git a/src/webkit/TaskQueue.ts b/src/webkit/TaskQueue.ts deleted file mode 100644 index 99b2de4469..0000000000 --- a/src/webkit/TaskQueue.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2019 Google Inc. All rights reserved. - * Modifications copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export class TaskQueue { - private _chain: Promise; - - constructor() { - this._chain = Promise.resolve(); - } - - postTask(task: () => any): Promise { - const result = this._chain.then(task); - this._chain = result.catch(() => {}); - return result; - } -} From 81c8a43e4bc41bc0d2a6b7590409ad0347e74160 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 5 Dec 2019 14:57:44 -0800 Subject: [PATCH 07/42] chore(ci): add travis deps to run WebKit Linux (#159) --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7143ca7b01..8c32d07c53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,16 @@ addons: packages: # This is required to run new chrome on old trusty - libnss3 + # These are required to run webkit + - libwoff1 + - libopus0 + - libwebp6 + - libwebpdemux2 + - libenchant1c2a + - libgudev-1.0-0 + - libsecret-1-0 + - libhyphen0 + - libgdk-pixbuf2.0-0 notifications: email: false cache: From fe34ddf766aba96673c03f08e47c8baab4992d70 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 5 Dec 2019 15:13:20 -0800 Subject: [PATCH 08/42] chore(ci): attempt to bring back travis to life --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8c32d07c53..97b6ebc5ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ addons: packages: # This is required to run new chrome on old trusty - libnss3 - # These are required to run webkit - libwoff1 - libopus0 - libwebp6 From e4fad11c1630dcb3290afea9b1eb52cf2a494381 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 5 Dec 2019 15:18:55 -0800 Subject: [PATCH 09/42] chore(ci): use bionic for travis --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 97b6ebc5ea..5c55d19f65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ language: node_js -dist: trusty +dist: bionic addons: apt: packages: # This is required to run new chrome on old trusty - - libnss3 + # - libnss3 + # This is required to run webkit - libwoff1 - libopus0 - libwebp6 From 39b22b41c5b4d2286b8bde859d0183a692a9d614 Mon Sep 17 00:00:00 2001 From: Joel Einbinder Date: Thu, 5 Dec 2019 16:26:09 -0800 Subject: [PATCH 10/42] feat: make JSHandle generic (#140) This makes it so that JSHandles and ElementHandles are aware of what types they point to. As a fun bonus, `$eval('input')` knows its going to get an HTMLInputElement. Most of this patch is casting things where previously we just assumed ElementHandles held the right kind of node. This gets us closer to being able to turn on `noImplicityAny` as well. #6 --- src/chromium/ExecutionContext.ts | 2 +- src/chromium/JSHandle.ts | 4 +-- src/chromium/Page.ts | 6 ++-- src/chromium/Playwright.ts | 2 +- src/dom.ts | 59 +++++++++++++++++++++----------- src/firefox/ExecutionContext.ts | 2 +- src/firefox/JSHandle.ts | 5 +-- src/frames.ts | 6 ++-- src/injected/injected.ts | 12 ++++--- src/input.ts | 10 +++--- src/javascript.ts | 10 +++--- src/types.ts | 16 +++++---- src/webkit/ExecutionContext.ts | 2 +- src/webkit/JSHandle.ts | 2 +- 14 files changed, 84 insertions(+), 54 deletions(-) diff --git a/src/chromium/ExecutionContext.ts b/src/chromium/ExecutionContext.ts index 89d69a952d..12a4443a48 100644 --- a/src/chromium/ExecutionContext.ts +++ b/src/chromium/ExecutionContext.ts @@ -149,7 +149,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate { await releaseObject(this._client, toRemoteObject(handle)); } - async handleJSONValue(handle: js.JSHandle): Promise { + async handleJSONValue(handle: js.JSHandle): Promise { const remoteObject = toRemoteObject(handle); if (remoteObject.objectId) { const response = await this._client.send('Runtime.callFunctionOn', { diff --git a/src/chromium/JSHandle.ts b/src/chromium/JSHandle.ts index 8163f5c3f4..8551557d97 100644 --- a/src/chromium/JSHandle.ts +++ b/src/chromium/JSHandle.ts @@ -99,11 +99,11 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate { await handle.evaluate(input.setFileInputFunction, files); } - async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise { + async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise> { const nodeInfo = await this._client.send('DOM.describeNode', { objectId: toRemoteObject(handle).objectId, }); - return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to); + return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to) as Promise>; } async adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.DOMWorld): Promise { diff --git a/src/chromium/Page.ts b/src/chromium/Page.ts index a9a68ad8d6..c6ab4d6a6b 100644 --- a/src/chromium/Page.ts +++ b/src/chromium/Page.ts @@ -223,7 +223,7 @@ export class Page extends EventEmitter { this._timeoutSettings.setDefaultTimeout(timeout); } - async $(selector: string | types.Selector): Promise { + async $(selector: string | types.Selector): Promise | null> { return this.mainFrame().$(selector); } @@ -240,11 +240,11 @@ export class Page extends EventEmitter { return this.mainFrame().$$eval(selector, pageFunction, ...args as any); } - async $$(selector: string | types.Selector): Promise { + async $$(selector: string | types.Selector): Promise[]> { return this.mainFrame().$$(selector); } - async $x(expression: string): Promise { + async $x(expression: string): Promise[]> { return this.mainFrame().$x(expression); } diff --git a/src/chromium/Playwright.ts b/src/chromium/Playwright.ts index 076af17086..b9e22f27c5 100644 --- a/src/chromium/Playwright.ts +++ b/src/chromium/Playwright.ts @@ -35,7 +35,7 @@ export class Playwright { this.downloadBrowser = download.bind(null, this.createBrowserFetcher(), preferredRevision, 'Chromium'); } - launch(options: (LauncherLaunchOptions & LauncherChromeArgOptions & LauncherBrowserOptions) | undefined): Promise { + launch(options?: (LauncherLaunchOptions & LauncherChromeArgOptions & LauncherBrowserOptions) | undefined): Promise { return this._launcher.launch(options); } diff --git a/src/dom.ts b/src/dom.ts index 458ecb53c0..17bb39404c 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -24,10 +24,10 @@ export interface DOMWorldDelegate { boundingBox(handle: ElementHandle): Promise; screenshot(handle: ElementHandle, options?: types.ScreenshotOptions): Promise; setInputFiles(handle: ElementHandle, files: input.FilePayload[]): Promise; - adoptElementHandle(handle: ElementHandle, to: DOMWorld): Promise; + adoptElementHandle(handle: ElementHandle, to: DOMWorld): Promise>; } -export type ScopedSelector = types.Selector & { scope?: ElementHandle }; +type ScopedSelector = types.Selector & { scope?: ElementHandle }; type ResolvedSelector = { scope?: ElementHandle, selector: string, visible?: boolean, disposeScope?: boolean }; export class DOMWorld { @@ -60,7 +60,7 @@ export class DOMWorld { return this._injectedPromise; } - async adoptElementHandle(handle: ElementHandle): Promise { + async adoptElementHandle(handle: ElementHandle): Promise> { assert(handle.executionContext() !== this.context, 'Should not adopt to the same context'); return this.delegate.adoptElementHandle(handle, this); } @@ -75,10 +75,10 @@ export class DOMWorld { return { scope: selector.scope, selector: normalizeSelector(selector.selector), visible: selector.visible }; } - async $(selector: string | ScopedSelector): Promise { + async $(selector: string | ScopedSelector): Promise | null> { const resolved = await this.resolveSelector(selector); const handle = await this.context.evaluateHandle( - (injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => { + (injected: Injected, selector: string, scope?: Node, visible?: boolean) => { const element = injected.querySelector(selector, scope || document); if (visible === undefined || !element) return element; @@ -93,10 +93,10 @@ export class DOMWorld { return handle.asElement(); } - async $$(selector: string | ScopedSelector): Promise { + async $$(selector: string | ScopedSelector): Promise[]> { const resolved = await this.resolveSelector(selector); const arrayHandle = await this.context.evaluateHandle( - (injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => { + (injected: Injected, selector: string, scope?: Node, visible?: boolean) => { const elements = injected.querySelectorAll(selector, scope || document); if (visible !== undefined) return elements.filter(element => injected.isVisible(element) === visible); @@ -131,7 +131,7 @@ export class DOMWorld { $$eval: types.$$Eval = async (selector, pageFunction, ...args) => { const resolved = await this.resolveSelector(selector); const arrayHandle = await this.context.evaluateHandle( - (injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => { + (injected: Injected, selector: string, scope?: Node, visible?: boolean) => { const elements = injected.querySelectorAll(selector, scope || document); if (visible !== undefined) return elements.filter(element => injected.isVisible(element) === visible); @@ -145,7 +145,7 @@ export class DOMWorld { } } -export class ElementHandle extends js.JSHandle { +export class ElementHandle extends js.JSHandle { private readonly _world: DOMWorld; constructor(context: js.ExecutionContext, remoteObject: any) { @@ -154,7 +154,7 @@ export class ElementHandle extends js.JSHandle { this._world = context._domWorld; } - asElement(): ElementHandle | null { + asElement(): ElementHandle | null { return this; } @@ -163,13 +163,15 @@ export class ElementHandle extends js.JSHandle { } async _scrollIntoViewIfNeeded() { - const error = await this.evaluate(async (element, pageJavascriptEnabled) => { - if (!element.isConnected) + const error = await this.evaluate(async (node: Node, pageJavascriptEnabled: boolean) => { + if (!node.isConnected) return 'Node is detached from document'; - if (element.nodeType !== Node.ELEMENT_NODE) + if (node.nodeType !== Node.ELEMENT_NODE) return 'Node is not of type HTMLElement'; + const element = node as Element; // force-scroll if page's javascript is disabled. if (!pageJavascriptEnabled) { + //@ts-ignore because only Chromium still supports 'instant' element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'}); return false; } @@ -183,8 +185,10 @@ export class ElementHandle extends js.JSHandle { // there are rafs. requestAnimationFrame(() => {}); }); - if (visibleRatio !== 1.0) + if (visibleRatio !== 1.0) { + //@ts-ignore because only Chromium still supports 'instant' element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'}); + } return false; }, this._world.delegate.isJavascriptEnabled()); if (error) @@ -336,13 +340,25 @@ export class ElementHandle extends js.JSHandle { } async setInputFiles(...files: (string|input.FilePayload)[]) { - const multiple = await this.evaluate((element: HTMLInputElement) => !!element.multiple); + const multiple = await this.evaluate((node: Node) => { + if (node.nodeType !== Node.ELEMENT_NODE || (node as Element).tagName !== 'INPUT') + throw new Error('Node is not an HTMLInputElement'); + const input = node as HTMLInputElement; + return input.multiple; + }); assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!'); await this._world.delegate.setInputFiles(this, await input.loadFiles(files)); } async focus() { - await this.evaluate(element => element.focus()); + const errorMessage = await this.evaluate((element: Node) => { + if (!element['focus']) + return 'Node is not an HTML or SVG element.'; + (element as HTMLElement|SVGElement).focus(); + return false; + }); + if (errorMessage) + throw new Error(errorMessage); } async type(text: string, options: { delay: (number | undefined); } | undefined) { @@ -374,7 +390,7 @@ export class ElementHandle extends js.JSHandle { return this._world.$(this._scopedSelector(selector)); } - $$(selector: string | types.Selector): Promise { + $$(selector: string | types.Selector): Promise[]> { return this._world.$$(this._scopedSelector(selector)); } @@ -386,12 +402,15 @@ export class ElementHandle extends js.JSHandle { return this._world.$$eval(this._scopedSelector(selector), pageFunction, ...args as any); } - $x(expression: string): Promise { + $x(expression: string): Promise[]> { return this._world.$$({ scope: this, selector: 'xpath=' + expression }); } isIntersectingViewport(): Promise { - return this.evaluate(async element => { + return this.evaluate(async (node: Node) => { + if (node.nodeType !== Node.ELEMENT_NODE) + throw new Error('Node is not of type HTMLElement'); + const element = node as Element; const visibleRatio = await new Promise(resolve => { const observer = new IntersectionObserver(entries => { resolve(entries[0].intersectionRatio); @@ -441,7 +460,7 @@ export function waitForFunctionTask(pageFunction: Function | string, options: ty export function waitForSelectorTask(selector: string | types.Selector, timeout: number): Task { return async (domWorld: DOMWorld) => { const resolved = await domWorld.resolveSelector(selector); - return domWorld.context.evaluateHandle((injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined, timeout: number) => { + return domWorld.context.evaluateHandle((injected: Injected, selector: string, scope: Node | undefined, visible: boolean | undefined, timeout: number) => { if (visible !== undefined) return injected.pollRaf(predicate, timeout); return injected.pollMutation(predicate, timeout); diff --git a/src/firefox/ExecutionContext.ts b/src/firefox/ExecutionContext.ts index 19ed5058db..cdb61b750e 100644 --- a/src/firefox/ExecutionContext.ts +++ b/src/firefox/ExecutionContext.ts @@ -134,7 +134,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate { }); } - async handleJSONValue(handle: js.JSHandle): Promise { + async handleJSONValue(handle: js.JSHandle): Promise { const payload = handle._remoteObject; if (!payload.objectId) return deserializeValue(payload); diff --git a/src/firefox/JSHandle.ts b/src/firefox/JSHandle.ts index c3e6f10fcc..139667a9c8 100644 --- a/src/firefox/JSHandle.ts +++ b/src/firefox/JSHandle.ts @@ -22,6 +22,7 @@ import * as types from '../types'; import * as frames from '../frames'; import { JugglerSession } from './Connection'; import { FrameManager } from './FrameManager'; +import { Protocol } from './protocol'; export class DOMWorldDelegate implements dom.DOMWorldDelegate { readonly keyboard: input.Keyboard; @@ -101,13 +102,13 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate { await handle.evaluate(input.setFileInputFunction, files); } - async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise { + async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise> { assert(false, 'Multiple isolated worlds are not implemented'); return handle; } } -function toRemoteObject(handle: dom.ElementHandle): any { +function toRemoteObject(handle: dom.ElementHandle): Protocol.RemoteObject { return handle._remoteObject; } diff --git a/src/frames.ts b/src/frames.ts index fefe473a21..36ed432d70 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -122,12 +122,12 @@ export class Frame { return context.evaluate(pageFunction, ...args as any); } - async $(selector: string | types.Selector): Promise { + async $(selector: string | types.Selector): Promise | null> { const domWorld = await this._mainDOMWorld(); return domWorld.$(types.clearSelector(selector)); } - async $x(expression: string): Promise { + async $x(expression: string): Promise[]> { const domWorld = await this._mainDOMWorld(); return domWorld.$$('xpath=' + expression); } @@ -142,7 +142,7 @@ export class Frame { return domWorld.$$eval(selector, pageFunction, ...args as any); } - async $$(selector: string | types.Selector): Promise { + async $$(selector: string | types.Selector): Promise[]> { const domWorld = await this._mainDOMWorld(); return domWorld.$$(types.clearSelector(selector)); } diff --git a/src/injected/injected.ts b/src/injected/injected.ts index 7f46facc3b..046ffa3169 100644 --- a/src/injected/injected.ts +++ b/src/injected/injected.ts @@ -17,9 +17,11 @@ class Injected { this.engines.set(engine.name, engine); } - querySelector(selector: string, root: SelectorRoot): Element | undefined { + querySelector(selector: string, root: Node): Element | undefined { const parsed = this._parseSelector(selector); - let element = root; + if (!root["querySelector"]) + throw new Error('Node is not queryable.'); + let element = root as SelectorRoot; for (const { engine, selector } of parsed) { const next = engine.query((element as Element).shadowRoot || element, selector); if (!next) @@ -29,9 +31,11 @@ class Injected { return element as Element; } - querySelectorAll(selector: string, root: SelectorRoot): Element[] { + querySelectorAll(selector: string, root: Node): Element[] { const parsed = this._parseSelector(selector); - let set = new Set([ root ]); + if (!root["querySelectorAll"]) + throw new Error('Node is not queryable.'); + let set = new Set([ root as SelectorRoot ]); for (const { engine, selector } of parsed) { const newSet = new Set(); for (const prev of set) { diff --git a/src/input.ts b/src/input.ts index 5e4e2885e3..829886ded1 100644 --- a/src/input.ts +++ b/src/input.ts @@ -287,9 +287,10 @@ export class Mouse { } } -export const selectFunction = (element: HTMLSelectElement, ...optionsToSelect: (Node | SelectOption)[]) => { - if (element.nodeName.toLowerCase() !== 'select') +export const selectFunction = (node: Node, ...optionsToSelect: (Node | SelectOption)[]) => { + if (node.nodeName.toLowerCase() !== 'select') throw new Error('Element is not a