From 90367c1f66ca8d846914909b4e897c226d183d85 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 11 Feb 2020 17:38:41 -0800 Subject: [PATCH 1/2] browser(webkit): emulate active and focused state (#940) --- browser_patches/webkit/BUILD_NUMBER | 2 +- browser_patches/webkit/patches/bootstrap.diff | 122 +++++++++++++----- 2 files changed, 90 insertions(+), 34 deletions(-) diff --git a/browser_patches/webkit/BUILD_NUMBER b/browser_patches/webkit/BUILD_NUMBER index b4fd007c7a..e29bf83e37 100644 --- a/browser_patches/webkit/BUILD_NUMBER +++ b/browser_patches/webkit/BUILD_NUMBER @@ -1 +1 @@ -1142 +1143 diff --git a/browser_patches/webkit/patches/bootstrap.diff b/browser_patches/webkit/patches/bootstrap.diff index 6a28d4a7b9..010770f8c5 100644 --- a/browser_patches/webkit/patches/bootstrap.diff +++ b/browser_patches/webkit/patches/bootstrap.diff @@ -731,10 +731,10 @@ index 0000000000000000000000000000000000000000..79edea03fed4e9be5da96e1275e182a4 +} diff --git a/Source/JavaScriptCore/inspector/protocol/Emulation.json b/Source/JavaScriptCore/inspector/protocol/Emulation.json new file mode 100644 -index 0000000000000000000000000000000000000000..bcf863e4bba3b99f66e75dabfc4d8c1289cc2b78 +index 0000000000000000000000000000000000000000..552e5dd60fa53fada79f8d6e333f52bc10a2bead --- /dev/null +++ b/Source/JavaScriptCore/inspector/protocol/Emulation.json -@@ -0,0 +1,32 @@ +@@ -0,0 +1,39 @@ +{ + "domain": "Emulation", + "availability": ["web"], @@ -764,6 +764,13 @@ index 0000000000000000000000000000000000000000..bcf863e4bba3b99f66e75dabfc4d8c12 + { "name": "username", "type": "string", "optional": true }, + { "name": "password", "type": "string", "optional": true } + ] ++ }, ++ { ++ "name": "setActiveAndFocused", ++ "description": "Makes page focused for test.", ++ "parameters": [ ++ { "name": "active", "type": "boolean", "optional": true } ++ ] + } + ] +} @@ -8723,10 +8730,10 @@ index 846a5aa27dfab3d274cffa4873861f2587d17fd8..cf0dc99f5601636c48abff09cd47ace4 diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..78815bc17978a23ca811ff99039b4fe86e6fb4a0 +index 0000000000000000000000000000000000000000..466d5129363e8b3d2e7cfc10f2a1ac7ae77f634f --- /dev/null +++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.cpp -@@ -0,0 +1,95 @@ +@@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -8821,13 +8828,21 @@ index 0000000000000000000000000000000000000000..78815bc17978a23ca811ff99039b4fe8 + m_page.setAuthCredentialsForAutomation(Optional()); +} + ++void WebPageInspectorEmulationAgent::setActiveAndFocused(Inspector::ErrorString&, const bool* active) ++{ ++ Optional value; ++ if (active) ++ value = *active; ++ m_page.setActiveForAutomation(value); ++} ++ +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h new file mode 100644 -index 0000000000000000000000000000000000000000..77dff2c191fee081773bc5705d80168c3898f496 +index 0000000000000000000000000000000000000000..43d827233df725fa8c85fc9ff4b20a1f716803e0 --- /dev/null +++ b/Source/WebKit/UIProcess/WebPageInspectorEmulationAgent.h -@@ -0,0 +1,66 @@ +@@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -8885,6 +8900,7 @@ index 0000000000000000000000000000000000000000..77dff2c191fee081773bc5705d80168c + void setDeviceMetricsOverride(int width, int height, double deviceScaleFactor, bool fixedlayout, Ref&&) override; + void setJavaScriptEnabled(Inspector::ErrorString&, bool enabled) override; + void setAuthCredentials(Inspector::ErrorString&, const String*, const String*) override; ++ void setActiveAndFocused(Inspector::ErrorString&, const bool*) override; + +private: + void platformSetSize(int width, int height, Function&&); @@ -9235,7 +9251,7 @@ index 0000000000000000000000000000000000000000..76290475097e756e3d932d22be4d8c79 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp -index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e7772056b250ee5 100644 +index 059e904a16c6c4b91d6271e25d239b975191facd..df51560a3abc953030e7cb7602275c0a9ab72281 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp @@ -905,6 +905,7 @@ void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason) @@ -9268,18 +9284,30 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 RefPtr WebPageProxy::loadRequest(ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData) { if (m_isClosed) -@@ -1680,6 +1696,19 @@ void WebPageProxy::setControlledByAutomation(bool controlled) +@@ -1680,6 +1696,31 @@ void WebPageProxy::setControlledByAutomation(bool controlled) m_process->processPool().sendToNetworkingProcess(Messages::NetworkProcess::SetSessionIsControlledByAutomation(m_websiteDataStore->sessionID(), m_controlledByAutomation)); } -+void WebPageProxy::setAuthCredentialsForAutomation(Optional&& credentials) { ++void WebPageProxy::setAuthCredentialsForAutomation(Optional&& credentials) ++{ + m_credentialsForAutomation = WTFMove(credentials); +} + -+void WebPageProxy::setPermissionsForAutomation(const HashMap>& permissions) { ++void WebPageProxy::setPermissionsForAutomation(const HashMap>& permissions) ++{ + m_permissionsForAutomation = permissions; +} + ++void WebPageProxy::setActiveForAutomation(Optional active) { ++ m_activeForAutomation = active; ++ OptionSet state; ++ state.add(ActivityState::IsFocused); ++ state.add(ActivityState::WindowIsActive); ++ state.add(ActivityState::IsVisible); ++ state.add(ActivityState::IsVisibleOrOccluded); ++ activityStateDidChange(state); ++} ++ +void WebPageProxy::logToStderr(const String& str) +{ + fprintf(stderr, "RENDERER: %s\n", str.utf8().data()); @@ -9288,7 +9316,33 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 void WebPageProxy::createInspectorTarget(const String& targetId, Inspector::InspectorTargetType type) { MESSAGE_CHECK(m_process, !targetId.isEmpty()); -@@ -2767,7 +2796,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b) +@@ -1822,6 +1863,25 @@ void WebPageProxy::updateActivityState(OptionSet flagsToUpd + { + bool wasVisible = isViewVisible(); + m_activityState.remove(flagsToUpdate); ++ ++ ++ if (m_activeForAutomation) { ++ if (*m_activeForAutomation) { ++ if (flagsToUpdate & ActivityState::IsFocused) ++ m_activityState.add(ActivityState::IsFocused); ++ if (flagsToUpdate & ActivityState::WindowIsActive) ++ m_activityState.add(ActivityState::WindowIsActive); ++ if (flagsToUpdate & ActivityState::IsVisible) ++ m_activityState.add(ActivityState::IsVisible); ++ if (flagsToUpdate & ActivityState::IsVisibleOrOccluded) ++ m_activityState.add(ActivityState::IsVisibleOrOccluded); ++ } ++ flagsToUpdate.remove(ActivityState::IsFocused); ++ flagsToUpdate.remove(ActivityState::WindowIsActive); ++ flagsToUpdate.remove(ActivityState::IsVisible); ++ flagsToUpdate.remove(ActivityState::IsVisibleOrOccluded); ++ } ++ + if (flagsToUpdate & ActivityState::IsFocused && pageClient().isViewFocused()) + m_activityState.add(ActivityState::IsFocused); + if (flagsToUpdate & ActivityState::WindowIsActive && pageClient().isViewWindowActive()) +@@ -2767,7 +2827,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b) void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent) { @@ -9297,7 +9351,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 const EventNames& names = eventNames(); for (auto& touchPoint : touchStartEvent.touchPoints()) { IntPoint location = touchPoint.location(); -@@ -2800,7 +2829,7 @@ void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent +@@ -2800,7 +2860,7 @@ void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent m_touchAndPointerEventTracking.touchStartTracking = TrackingType::Synchronous; m_touchAndPointerEventTracking.touchMoveTracking = TrackingType::Synchronous; m_touchAndPointerEventTracking.touchEndTracking = TrackingType::Synchronous; @@ -9306,7 +9360,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 } TrackingType WebPageProxy::touchEventTrackingType(const WebTouchEvent& touchStartEvent) const -@@ -3135,6 +3164,7 @@ void WebPageProxy::receivedNavigationPolicyDecision(PolicyAction policyAction, A +@@ -3135,6 +3195,7 @@ void WebPageProxy::receivedNavigationPolicyDecision(PolicyAction policyAction, A void WebPageProxy::receivedPolicyDecision(PolicyAction action, API::Navigation* navigation, Optional&& websitePolicies, Ref&& sender, WillContinueLoadInNewProcess willContinueLoadInNewProcess) { @@ -9314,7 +9368,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 if (!hasRunningProcess()) { sender->send(PolicyAction::Ignore, 0, DownloadID(), WTF::nullopt); return; -@@ -4228,6 +4258,7 @@ void WebPageProxy::didDestroyNavigation(uint64_t navigationID) +@@ -4228,6 +4289,7 @@ void WebPageProxy::didDestroyNavigation(uint64_t navigationID) // FIXME: Message check the navigationID. m_navigationState->didDestroyNavigation(navigationID); @@ -9322,7 +9376,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 } void WebPageProxy::didStartProvisionalLoadForFrame(FrameIdentifier frameID, uint64_t navigationID, URL&& url, URL&& unreachableURL, const UserData& userData) -@@ -4449,6 +4480,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref&& p +@@ -4449,6 +4511,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref&& p m_failingProvisionalLoadURL = { }; @@ -9331,7 +9385,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 // If the provisional page's load fails then we destroy the provisional page. if (m_provisionalPage && m_provisionalPage->mainFrame() == frame && willContinueLoading == WillContinueLoading::No) m_provisionalPage = nullptr; -@@ -4886,8 +4919,16 @@ void WebPageProxy::decidePolicyForNavigationActionAsync(FrameIdentifier frameID, +@@ -4886,8 +4950,16 @@ void WebPageProxy::decidePolicyForNavigationActionAsync(FrameIdentifier frameID, NavigationActionData&& navigationActionData, FrameInfoData&& frameInfoData, Optional originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request, IPC::FormDataReference&& requestBody, WebCore::ResourceResponse&& redirectResponse, const UserData& userData, uint64_t listenerID) { @@ -9350,7 +9404,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 } void WebPageProxy::decidePolicyForNavigationActionAsyncShared(Ref&& process, PageIdentifier webPageID, FrameIdentifier frameID, WebCore::SecurityOriginData&& frameSecurityOrigin, -@@ -5459,6 +5500,8 @@ void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, SecurityOriginDat +@@ -5459,6 +5531,8 @@ void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, SecurityOriginDat if (auto* automationSession = process().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -9359,7 +9413,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 m_uiClient->runJavaScriptAlert(*this, message, frame, WTFMove(securityOrigin), WTFMove(reply)); } -@@ -5478,6 +5521,8 @@ void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, SecurityOriginD +@@ -5478,6 +5552,8 @@ void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, SecurityOriginD if (auto* automationSession = process().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -9368,7 +9422,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 m_uiClient->runJavaScriptConfirm(*this, message, frame, WTFMove(securityOrigin), WTFMove(reply)); } -@@ -5497,6 +5542,8 @@ void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, SecurityOriginDa +@@ -5497,6 +5573,8 @@ void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, SecurityOriginDa if (auto* automationSession = process().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -9377,7 +9431,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 m_uiClient->runJavaScriptPrompt(*this, message, defaultValue, frame, WTFMove(securityOrigin), WTFMove(reply)); } -@@ -5656,6 +5703,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, Security +@@ -5656,6 +5734,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, Security return; } } @@ -9386,7 +9440,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 // Since runBeforeUnloadConfirmPanel() can spin a nested run loop we need to turn off the responsiveness timer and the tryClose timer. m_process->stopResponsivenessTimer(); -@@ -6715,6 +6764,7 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) +@@ -6715,6 +6795,7 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) if (auto* automationSession = process().processPool().automationSession()) automationSession->mouseEventsFlushedForPage(*this); didFinishProcessingAllPendingMouseEvents(); @@ -9394,7 +9448,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 } break; -@@ -6741,7 +6791,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) +@@ -6741,7 +6822,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()); @@ -9402,7 +9456,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 MESSAGE_CHECK(m_process, !m_keyEventQueue.isEmpty()); NativeWebKeyboardEvent event = m_keyEventQueue.takeFirst(); -@@ -6761,7 +6810,6 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) +@@ -6761,7 +6841,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); @@ -9410,7 +9464,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 pageClient().doneWithKeyEvent(event, handled); if (!handled) m_uiClient->didNotHandleKeyEvent(this, event); -@@ -6770,6 +6818,7 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) +@@ -6770,6 +6849,7 @@ void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled) if (!canProcessMoreKeyEvents) { if (auto* automationSession = process().processPool().automationSession()) automationSession->keyboardEventsFlushedForPage(*this); @@ -9418,7 +9472,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 } break; } -@@ -7238,8 +7287,10 @@ static bool shouldReloadAfterProcessTermination(ProcessTerminationReason reason) +@@ -7238,8 +7318,10 @@ static bool shouldReloadAfterProcessTermination(ProcessTerminationReason reason) void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason) { RELEASE_LOG_IF_ALLOWED(Loading, "dispatchProcessDidTerminate: reason = %d", reason); @@ -9430,7 +9484,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 if (m_loaderClient) handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this); else -@@ -7712,6 +7763,14 @@ void WebPageProxy::gamepadActivity(const Vector& gamepadDatas, bool +@@ -7712,6 +7794,14 @@ void WebPageProxy::gamepadActivity(const Vector& gamepadDatas, bool void WebPageProxy::didReceiveAuthenticationChallengeProxy(Ref&& authenticationChallenge, NegotiatedLegacyTLS negotiatedLegacyTLS) { @@ -9445,7 +9499,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes) { m_navigationClient->shouldAllowLegacyTLS(*this, authenticationChallenge.get(), [this, protectedThis = makeRef(*this), authenticationChallenge = authenticationChallenge.copyRef()] (bool shouldAllowLegacyTLS) { if (shouldAllowLegacyTLS) -@@ -7794,7 +7853,8 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID, +@@ -7794,7 +7884,8 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID, MESSAGE_CHECK(m_process, securityOriginData); // FIXME: Geolocation should probably be using toString() as its string representation instead of databaseIdentifier(). @@ -9455,7 +9509,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 auto request = m_geolocationPermissionRequestManager.createRequest(geolocationID); Function completionHandler = [request = WTFMove(request)](bool allowed) { if (allowed) -@@ -7802,6 +7862,11 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID, +@@ -7802,6 +7893,11 @@ void WebPageProxy::requestGeolocationPermissionForFrame(uint64_t geolocationID, else request->deny(); }; @@ -9468,7 +9522,7 @@ index 059e904a16c6c4b91d6271e25d239b975191facd..0c5038d790e2c5913b1bb8344e777205 // FIXME: Once iOS migrates to the new WKUIDelegate SPI, clean this up // and make it one UIClient call that calls the completionHandler with false diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h -index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c3605f8e4 100644 +index b3856a4f9e7859bf3e0b9957c95040a784e9b317..2d154febc73de4877463e5099ffdc3002f6dfd2b 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h @@ -35,6 +35,7 @@ @@ -9488,18 +9542,19 @@ index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c #if PLATFORM(IOS_FAMILY) void showInspectorIndication(); -@@ -555,6 +558,10 @@ public: +@@ -555,6 +558,11 @@ public: void setPageLoadStateObserver(std::unique_ptr&&); + void setAuthCredentialsForAutomation(Optional&&); + void setPermissionsForAutomation(const HashMap>&); ++ void setActiveForAutomation(Optional active); + void logToStderr(const String& str); + void initializeWebPage(); void setDrawingArea(std::unique_ptr&&); -@@ -580,6 +587,7 @@ public: +@@ -580,6 +588,7 @@ public: void closePage(); void addPlatformLoadParameters(LoadParameters&); @@ -9507,7 +9562,7 @@ index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c RefPtr loadRequest(WebCore::ResourceRequest&&, WebCore::ShouldOpenExternalURLsPolicy = WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes, API::Object* userData = nullptr); RefPtr loadFile(const String& fileURL, const String& resourceDirectoryURL, API::Object* userData = nullptr); RefPtr loadData(const IPC::DataReference&, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData = nullptr, WebCore::ShouldOpenExternalURLsPolicy = WebCore::ShouldOpenExternalURLsPolicy::ShouldNotAllow); -@@ -2293,6 +2301,7 @@ private: +@@ -2293,6 +2302,7 @@ private: String m_overrideContentSecurityPolicy; RefPtr m_inspector; @@ -9515,12 +9570,13 @@ index b3856a4f9e7859bf3e0b9957c95040a784e9b317..5d51d3e9e03b375a2cbd01f909e9b27c #if ENABLE(FULLSCREEN_API) std::unique_ptr m_fullScreenManager; -@@ -2707,6 +2716,8 @@ private: +@@ -2707,6 +2717,9 @@ private: bool m_isLayerTreeFrozenDueToSwipeAnimation { false }; String m_overriddenMediaType; + Optional m_credentialsForAutomation; + HashMap> m_permissionsForAutomation; ++ Optional m_activeForAutomation; #if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION) std::unique_ptr m_webDeviceOrientationUpdateProviderProxy; From da30847c83ff8138d466cccace3eefd971b4ba2d Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 11 Feb 2020 18:52:01 -0800 Subject: [PATCH 2/2] feat(firefox): apply emulation to all pages in the browser context (#931) --- package.json | 2 +- src/firefox/ffBrowser.ts | 72 +++++++++------- src/firefox/ffConnection.ts | 5 +- src/firefox/ffPage.ts | 49 +++-------- test/browsercontext.spec.js | 13 --- test/launcher.spec.js | 2 +- test/page.spec.js | 67 +-------------- test/playwright.spec.js | 1 + test/popup.spec.js | 158 ++++++++++++++++++++++++++++++++++++ 9 files changed, 218 insertions(+), 151 deletions(-) create mode 100644 test/popup.spec.js diff --git a/package.json b/package.json index 0833782fbf..1483882b14 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "main": "index.js", "playwright": { "chromium_revision": "740289", - "firefox_revision": "1025", + "firefox_revision": "1028", "webkit_revision": "1141" }, "scripts": { diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index 82cea3e284..6fc992abb5 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -18,11 +18,11 @@ import { Browser, createPageInNewContext } from '../browser'; import { BrowserContext, BrowserContextOptions } from '../browserContext'; import { Events } from '../events'; -import { assert, helper, RegisteredListener } from '../helper'; +import { assert, helper, RegisteredListener, debugError } from '../helper'; import * as network from '../network'; import * as types from '../types'; import { Page } from '../page'; -import { ConnectionEvents, FFConnection, FFSessionEvents } from './ffConnection'; +import { ConnectionEvents, FFConnection, FFSessionEvents, FFSession } from './ffConnection'; import { FFPage } from './ffPage'; import * as platform from '../platform'; import { Protocol } from './protocol'; @@ -68,8 +68,17 @@ export class FFBrowser extends platform.EventEmitter implements Browser { } async newContext(options: BrowserContextOptions = {}): Promise { + const viewport = options.viewport ? { + viewportSize: { width: options.viewport.width, height: options.viewport.height }, + isMobile: !!options.viewport.isMobile, + deviceScaleFactor: options.viewport.deviceScaleFactor || 1, + hasTouch: !!options.viewport.isMobile, + } : undefined; const {browserContextId} = await this._connection.send('Target.createBrowserContext', { - userAgent: options.userAgent + userAgent: options.userAgent, + bypassCSP: options.bypassCSP, + javaScriptDisabled: options.javaScriptEnabled === false ? true : undefined, + viewport, }); // TODO: move ignoreHTTPSErrors to browser context level. if (options.ignoreHTTPSErrors) @@ -121,14 +130,6 @@ export class FFBrowser extends platform.EventEmitter implements Browser { const context = browserContextId ? this._contexts.get(browserContextId)! : this._defaultContext; const target = new Target(this._connection, this, context, targetId, type, url, openerId); this._targets.set(targetId, target); - const opener = target.opener(); - if (opener && opener._pagePromise) { - const openerPage = await opener._pagePromise; - if (openerPage.listenerCount(Events.Page.Popup)) { - const popupPage = await target.page(); - openerPage.emit(Events.Page.Popup, popupPage); - } - } } _onTargetDestroyed(payload: Protocol.Target.targetDestroyedPayload) { @@ -144,11 +145,18 @@ export class FFBrowser extends platform.EventEmitter implements Browser { target._url = url; } - _onAttachedToTarget(payload: Protocol.Target.attachedToTargetPayload) { - const {targetId, type} = payload.targetInfo; + async _onAttachedToTarget(payload: Protocol.Target.attachedToTargetPayload) { + const {targetId} = payload.targetInfo; const target = this._targets.get(targetId)!; - if (type === 'page') - target.page(); + target._initPagePromise(this._connection.getSession(payload.sessionId)!); + const opener = target.opener(); + if (opener && opener._pagePromise) { + const openerPage = await opener._pagePromise; + if (openerPage.listenerCount(Events.Page.Popup)) { + const popupPage = await target.page(); + openerPage.emit(Events.Page.Popup, popupPage); + } + } } async close() { @@ -278,25 +286,27 @@ class Target { return this._context; } - page(): Promise { + async page(): Promise { if (this._type !== 'page') throw new Error(`Cannot create page for "${this._type}" target`); - if (!this._pagePromise) { - this._pagePromise = new Promise(async f => { - const session = await this._connection.createSession(this._targetId); - this._ffPage = new FFPage(session, this._context, async () => { - const openerTarget = this.opener(); - if (!openerTarget) - return null; - return await openerTarget.page(); - }); - const page = this._ffPage._page; - session.once(FFSessionEvents.Disconnected, () => page._didDisconnect()); - await this._ffPage._initialize(); - f(page); + if (!this._pagePromise) + await this._connection.send('Target.attachToTarget', {targetId: this._targetId}); + return this._pagePromise!; + } + + _initPagePromise(session: FFSession) { + this._pagePromise = new Promise(async f => { + this._ffPage = new FFPage(session, this._context, async () => { + const openerTarget = this.opener(); + if (!openerTarget) + return null; + return await openerTarget.page(); }); - } - return this._pagePromise; + const page = this._ffPage._page; + session.once(FFSessionEvents.Disconnected, () => page._didDisconnect()); + await this._ffPage._initialize().catch(debugError); + f(page); + }); } browser() { diff --git a/src/firefox/ffConnection.ts b/src/firefox/ffConnection.ts index 8ccb0f1c97..206ff0f710 100644 --- a/src/firefox/ffConnection.ts +++ b/src/firefox/ffConnection.ts @@ -143,9 +143,8 @@ export class FFConnection extends platform.EventEmitter { this._transport.close(); } - async createSession(targetId: string): Promise { - const {sessionId} = await this.send('Target.attachToTarget', {targetId}); - return this._sessions.get(sessionId)!; + getSession(sessionId: string): FFSession | null { + return this._sessions.get(sessionId) || null; } } diff --git a/src/firefox/ffPage.ts b/src/firefox/ffPage.ts index 96e58f6e20..4780a528de 100644 --- a/src/firefox/ffPage.ts +++ b/src/firefox/ffPage.ts @@ -77,26 +77,13 @@ export class FFPage implements PageDelegate { } async _initialize() { - const promises: Promise[] = [ - this._session.send('Runtime.enable').then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)), - this._session.send('Network.enable'), - this._session.send('Page.enable'), - ]; - const options = this._page.context()._options; - if (options.viewport) - promises.push(this._updateViewport()); - if (options.bypassCSP) - promises.push(this._session.send('Page.setBypassCSP', { enabled: true })); - if (options.javaScriptEnabled === false) - promises.push(this._session.send('Page.setJavascriptEnabled', { enabled: false })); - await Promise.all(promises); - } - - async _ensureIsolatedWorld(name: string) { - await this._session.send('Page.addScriptToEvaluateOnNewDocument', { - script: '', - worldName: name, - }); + await Promise.all([ + this._session.send('Page.addScriptToEvaluateOnNewDocument', { + script: '', + worldName: UTILITY_WORLD_NAME, + }), + new Promise(f => this._session.once('Page.ready', f)), + ]); } _onExecutionContextCreated(payload: Protocol.Runtime.executionContextCreatedPayload) { @@ -268,22 +255,10 @@ export class FFPage implements PageDelegate { async setViewportSize(viewportSize: types.Size): Promise { assert(this._page._state.viewportSize === viewportSize); - await this._updateViewport(); - } - - async _updateViewport() { - let viewport = this._page.context()._options.viewport || { width: 0, height: 0 }; - const viewportSize = this._page._state.viewportSize; - if (viewportSize) - viewport = { ...viewport, ...viewportSize }; - await this._session.send('Page.setViewport', { - viewport: { - width: viewport.width, - height: viewport.height, - isMobile: !!viewport.isMobile, - deviceScaleFactor: viewport.deviceScaleFactor || 1, - hasTouch: !!viewport.isMobile, - isLandscape: viewport.width > viewport.height + await this._session.send('Page.setViewportSize', { + viewportSize: { + width: viewportSize.width, + height: viewportSize.height, }, }); } @@ -373,7 +348,7 @@ export class FFPage implements PageDelegate { } async resetViewport(): Promise { - await this._session.send('Page.setViewport', { viewport: null }); + await this._session.send('Page.setViewportSize', { viewportSize: null }); } async getContentFrame(handle: dom.ElementHandle): Promise { diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index 6dc1e8a326..afc2bef868 100644 --- a/test/browsercontext.spec.js +++ b/test/browsercontext.spec.js @@ -34,19 +34,6 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE await context.close(); expect(browser.contexts().length).toBe(0); }); - it.skip(CHROMIUM)('popup should inherit user agent', async function({newContext, server}) { - const context = await newContext({ - userAgent: 'hey' - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const evaluatePromise = page.evaluate(url => window.open(url), server.PREFIX + '/dummy.html'); - const popupPromise = page.waitForEvent('popup'); - const request = await server.waitForRequest('/dummy.html'); - await evaluatePromise; - await popupPromise; - expect(request.headers['user-agent']).toBe('hey'); - }); it('window.open should use parent tab context', async function({newContext, server}) { const context = await newContext(); const page = await context.newPage(); diff --git a/test/launcher.spec.js b/test/launcher.spec.js index b097b427f3..08e87b7518 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -154,7 +154,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() }); const page = await remote.newPage(); const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e); - + // Make sure the previous waitForSelector has time to make it to the browser before we disconnect. await page.waitForSelector('body'); diff --git a/test/page.spec.js b/test/page.spec.js index e42d215218..3a8a176a72 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -125,70 +125,6 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF }); }); - describe('Page.Events.Popup', function() { - it('should work', async({page}) => { - const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), - page.evaluate(() => window.open('about:blank')), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(true); - }); - it('should work with noopener', async({page}) => { - const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), - page.evaluate(() => window.open('about:blank', null, 'noopener')), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - }); - it.skip(FFOX)('should work with clicking target=_blank', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), - page.click('a'), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(true); - }); - it.skip(FFOX)('should work with fake-clicking target=_blank and rel=noopener', async({page, server}) => { - // TODO: FFOX sends events for "one-style.html" request to both pages. - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), - page.$eval('a', a => a.click()), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - // TODO: At this point popup might still have about:blank as the current document. - // FFOX is slow enough to trigger this. We should do something about popups api. - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - }); - it.skip(FFOX)('should work with clicking target=_blank and rel=noopener', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), - page.click('a'), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - }); - it.skip(FFOX)('should not treat navigations as new popups', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), - page.click('a'), - ]); - let badSecondPopup = false; - page.on('popup', () => badSecondPopup = true); - await popup.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - expect(badSecondPopup).toBe(false); - }); - }); - describe('Page.opener', function() { it('should provide access to the opener page', async({page}) => { const [popup] = await Promise.all([ @@ -306,8 +242,9 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF describe('Page.Events.DOMContentLoaded', function() { it('should fire when expected', async({page, server}) => { - page.goto('about:blank'); + const navigatedPromise = page.goto('about:blank'); await waitEvent(page, 'domcontentloaded'); + await navigatedPromise; }); }); diff --git a/test/playwright.spec.js b/test/playwright.spec.js index 9385c7c093..63aedfebc1 100644 --- a/test/playwright.spec.js +++ b/test/playwright.spec.js @@ -197,6 +197,7 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => { testRunner.loadTests(require('./browser.spec.js'), testOptions); testRunner.loadTests(require('./browsercontext.spec.js'), testOptions); testRunner.loadTests(require('./ignorehttpserrors.spec.js'), testOptions); + testRunner.loadTests(require('./popup.spec.js'), testOptions); }); // Top-level tests that launch Browser themselves. diff --git a/test/popup.spec.js b/test/popup.spec.js new file mode 100644 index 0000000000..8a2364e8ca --- /dev/null +++ b/test/popup.spec.js @@ -0,0 +1,158 @@ +/** + * 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. + */ + +module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WEBKIT, FFOX}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit, dit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + + describe('window.open', function() { + it.skip(CHROMIUM)('should inherit user agent from browser context', async function({newContext, server}) { + const context = await newContext({ + userAgent: 'hey' + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const evaluatePromise = page.evaluate(url => window.open(url), server.PREFIX + '/dummy.html'); + const popupPromise = page.waitForEvent('popup'); + const request = await server.waitForRequest('/dummy.html'); + await evaluatePromise; + await popupPromise; + await context.close(); + expect(request.headers['user-agent']).toBe('hey'); + }); + it.skip(CHROMIUM)('should inherit touch support from browser context', async function({newContext, server}) { + const context = await newContext({ + viewport: { width: 400, height: 500, isMobile: true } + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const hasTouch = await page.evaluate(() => { + const win = window.open(''); + return 'ontouchstart' in win; + }); + await context.close(); + expect(hasTouch).toBe(true); + }); + it.skip(CHROMIUM || WEBKIT)('should inherit viewport size from browser context', async function({newContext, server}) { + const context = await newContext({ + viewport: { width: 400, height: 500, isMobile: true } + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const size = await page.evaluate(() => { + const win = window.open('about:blank'); + return { width: win.innerWidth, height: win.innerHeight }; + }); + await context.close(); + expect(size).toEqual({width: 400, height: 500}); + }); + }); + + describe('Page.Events.Popup', function() { + it('should work', async({newContext}) => { + const context = await newContext(); + const page = await context.newPage(); + const [popup] = await Promise.all([ + new Promise(x => page.once('popup', x)), + page.evaluate(() => window.__popup = window.open('about:blank')), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(true); + await context.close(); + }); + it.skip(CHROMIUM)('should work with empty url', async({newContext}) => { + const context = await newContext(); + const page = await context.newPage(); + const [popup] = await Promise.all([ + new Promise(x => page.once('popup', x)), + page.evaluate(() => window.__popup = window.open('')), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(true); + await context.close(); + }); + it('should work with noopener', async({newContext}) => { + const context = await newContext(); + const page = await context.newPage(); + const [popup] = await Promise.all([ + new Promise(x => page.once('popup', x)), + page.evaluate(() => window.__popup = window.open('about:blank', null, 'noopener')), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + await context.close(); + }); + it.skip(FFOX)('should work with clicking target=_blank', async({newContext, server}) => { + const context = await newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), + page.click('a'), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(true); + await context.close(); + }); + it.skip(FFOX)('should work with fake-clicking target=_blank and rel=noopener', async({newContext, server}) => { + const context = await newContext(); + const page = await context.newPage(); + // TODO: FFOX sends events for "one-style.html" request to both pages. + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), + page.$eval('a', a => a.click()), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + // TODO: At this point popup might still have about:blank as the current document. + // FFOX is slow enough to trigger this. We should do something about popups api. + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + await context.close(); + }); + it.skip(FFOX)('should work with clicking target=_blank and rel=noopener', async({newContext, server}) => { + const context = await newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), + page.click('a'), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + await context.close(); + }); + it.skip(FFOX)('should not treat navigations as new popups', async({newContext, server}) => { + const context = await newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup').then(async popup => { await popup.waitForLoadState(); return popup; }), + page.click('a'), + ]); + let badSecondPopup = false; + page.on('popup', () => badSecondPopup = true); + await popup.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + await context.close(); + expect(badSecondPopup).toBe(false); + }); + }); + +};