From 643ca7872b450ea4efacab6188849e5aac2ba161 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Tue, 15 Dec 2009 10:12:09 +0000 Subject: Merge webkit.org at r51976 : Initial merge by git. Change-Id: Ib0e7e2f0fb4bee5a186610272edf3186f0986b43 --- WebCore/loader/FrameLoader.cpp | 425 ++++++++++++++++++++++++----------------- 1 file changed, 249 insertions(+), 176 deletions(-) (limited to 'WebCore/loader/FrameLoader.cpp') diff --git a/WebCore/loader/FrameLoader.cpp b/WebCore/loader/FrameLoader.cpp index 98b8e61..22399b0 100644 --- a/WebCore/loader/FrameLoader.cpp +++ b/WebCore/loader/FrameLoader.cpp @@ -203,6 +203,7 @@ FrameLoader::FrameLoader(Frame* frame, FrameLoaderClient* client) , m_didPerformFirstNavigation(false) , m_loadingFromCachedPage(false) , m_suppressOpenerInNewFrame(false) + , m_sandboxFlags(SandboxAll) #ifndef NDEBUG , m_didDispatchDidCommitLoad(false) #endif @@ -235,6 +236,9 @@ void FrameLoader::init() m_frame->document()->cancelParsing(); m_creatingInitialEmptyDocument = false; m_didCallImplicitClose = true; + + // Propagate sandbox attributes to this Frameloader and its descendants. + updateSandboxFlags(); } void FrameLoader::setDefersLoading(bool defers) @@ -452,6 +456,9 @@ void FrameLoader::submitForm(const char* action, const String& url, PassRefPtrscript()->executeIfJavaScriptURL(u, false, false); @@ -530,7 +537,10 @@ void FrameLoader::stopLoading(UnloadEventPolicy unloadEventPolicy, DatabasePolic if (m_frame->domWindow()) { if (unloadEventPolicy == UnloadEventPolicyUnloadAndPageHide) m_frame->domWindow()->dispatchEvent(PageTransitionEvent::create(eventNames().pagehideEvent, m_frame->document()->inPageCache()), m_frame->document()); +<<<<<<< HEAD:WebCore/loader/FrameLoader.cpp #ifndef ANDROID_PAGE_CACHE_UNLOAD +======= +>>>>>>> webkit.org at r51976:WebCore/loader/FrameLoader.cpp if (!m_frame->document()->inPageCache()) #endif m_frame->domWindow()->dispatchEvent(Event::create(eventNames().unloadEvent, false, false), m_frame->domWindow()->document()); @@ -624,8 +634,8 @@ KURL FrameLoader::iconURL() KURL url; url.setProtocol(m_URL.protocol()); url.setHost(m_URL.host()); - if (int port = m_URL.port()) - url.setPort(port); + if (m_URL.hasPort()) + url.setPort(m_URL.port()); url.setPath("/favicon.ico"); return url; } @@ -759,7 +769,7 @@ void FrameLoader::receivedFirstData() begin(m_workingURL, false); dispatchDidCommitLoad(); - dispatchWindowObjectAvailable(); + dispatchDidClearWindowObjectsInAllWorlds(); if (m_documentLoader) { String ptitle = m_documentLoader->title(); @@ -848,7 +858,7 @@ void FrameLoader::begin(const KURL& url, bool dispatch, SecurityOrigin* origin) m_frame->domWindow()->setSecurityOrigin(document->securityOrigin()); if (dispatch) - dispatchWindowObjectAvailable(); + dispatchDidClearWindowObjectsInAllWorlds(); updateFirstPartyForCookies(); @@ -1285,14 +1295,18 @@ bool FrameLoader::requestObject(RenderPart* renderer, const String& url, const A if (!settings || !settings->arePluginsEnabled() || (!settings->isJavaEnabled() && MIMETypeRegistry::isJavaAppletMIMEType(mimeType))) return false; + if (isSandboxed(SandboxPlugins)) + return false; return loadPlugin(renderer, completedURL, mimeType, paramNames, paramValues, useFallback); } ASSERT(renderer->node()->hasTagName(objectTag) || renderer->node()->hasTagName(embedTag)); HTMLPlugInElement* element = static_cast(renderer->node()); - - // FIXME: OK to always make a new frame? When does the old frame get removed? - return loadSubframe(element, completedURL, frameName, m_outgoingReferrer); + + // If the plug-in element already contains a subframe, requestFrame will re-use it. Otherwise, + // it will create a new frame and set it as the RenderPart's widget, causing what was previously + // in the widget to be torn down. + return requestFrame(element, completedURL, frameName); } bool FrameLoader::shouldUsePlugin(const KURL& url, const String& mimeType, bool hasFallback, bool& useFallback) @@ -1730,52 +1744,70 @@ void FrameLoader::setFirstPartyForCookies(const KURL& url) child->loader()->setFirstPartyForCookies(url); } -class HashChangeEventTask : public ScriptExecutionContext::Task { -public: - static PassRefPtr create(PassRefPtr document) - { - return adoptRef(new HashChangeEventTask(document)); - } - - virtual void performTask(ScriptExecutionContext* context) - { - ASSERT_UNUSED(context, context->isDocument()); - m_document->dispatchWindowEvent(Event::create(eventNames().hashchangeEvent, false, false)); - } - -private: - HashChangeEventTask(PassRefPtr document) - : m_document(document) - { - ASSERT(m_document); - } - - RefPtr m_document; -}; - // This does the same kind of work that didOpenURL does, except it relies on the fact // that a higher level already checked that the URLs match and the scrolling is the right thing to do. -void FrameLoader::scrollToAnchor(const KURL& url) -{ - ASSERT(equalIgnoringFragmentIdentifier(url, m_URL)); - if (equalIgnoringFragmentIdentifier(url, m_URL) && !equalIgnoringNullity(url.fragmentIdentifier(), m_URL.fragmentIdentifier())) { - Document* currentDocument = frame()->document(); - currentDocument->postTask(HashChangeEventTask::create(currentDocument)); +void FrameLoader::loadInSameDocument(const KURL& url, SerializedScriptValue* stateObject, bool isNewNavigation) +{ + // If we have a state object, we cannot also be a new navigation. + ASSERT(!stateObject || (stateObject && !isNewNavigation)); + + // Update the data source's request with the new URL to fake the URL change + m_frame->document()->setURL(url); + documentLoader()->replaceRequestURLForSameDocumentNavigation(url); + if (isNewNavigation && !shouldTreatURLAsSameAsCurrent(url) && !stateObject) { + // NB: must happen after replaceRequestURLForSameDocumentNavigation(), since we add + // based on the current request. Must also happen before we openURL and displace the + // scroll position, since adding the BF item will save away scroll state. + + // NB2: If we were loading a long, slow doc, and the user anchor nav'ed before + // it was done, currItem is now set the that slow doc, and prevItem is whatever was + // before it. Adding the b/f item will bump the slow doc down to prevItem, even + // though its load is not yet done. I think this all works out OK, for one because + // we have already saved away the scroll and doc state for the long slow load, + // but it's not an obvious case. + + history()->updateBackForwardListForFragmentScroll(); } + bool hashChange = equalIgnoringFragmentIdentifier(url, m_URL) && url.fragmentIdentifier() != m_URL.fragmentIdentifier(); m_URL = url; - history()->updateForAnchorScroll(); + history()->updateForSameDocumentNavigation(); // If we were in the autoscroll/panScroll mode we want to stop it before following the link to the anchor - m_frame->eventHandler()->stopAutoscrollTimer(); - started(); - if (FrameView* view = m_frame->view()) - view->scrollToFragment(m_URL); - + if (hashChange) + m_frame->eventHandler()->stopAutoscrollTimer(); + // It's important to model this as a load that starts and immediately finishes. // Otherwise, the parent frame may think we never finished loading. + started(); + + if (hashChange) { + if (FrameView* view = m_frame->view()) + view->scrollToFragment(m_URL); + } + m_isComplete = false; checkCompleted(); + + if (isNewNavigation) { + // This will clear previousItem from the rest of the frame tree that didn't + // doing any loading. We need to make a pass on this now, since for anchor nav + // we'll not go through a real load and reach Completed state. + checkLoadComplete(); + } + + if (stateObject) { + m_frame->document()->statePopped(stateObject); + m_client->dispatchDidPopStateWithinPage(); + } + + if (hashChange) { + m_frame->document()->dispatchWindowEvent(Event::create(eventNames().hashchangeEvent, false, false)); + m_client->dispatchDidChangeLocationWithinPage(); + } + + // FrameLoaderClient::didFinishLoad() tells the internal load delegate the load finished with no error + m_client->didFinishLoad(); } bool FrameLoader::isComplete() const @@ -2256,6 +2288,10 @@ bool FrameLoader::shouldAllowNavigation(Frame* targetFrame) const if (m_frame == targetFrame) return true; + // A sandboxed frame can only navigate itself and its descendants. + if (isSandboxed(SandboxNavigation) && !targetFrame->tree()->isDescendantOf(m_frame)) + return false; + // Let a frame navigate the top-level window that contains it. This is // important to allow because it lets a site "frame-bust" (escape from a // frame created by another web site). @@ -2957,8 +2993,8 @@ void FrameLoader::checkLoadCompleteForThisFrame() // delegate callback. if (pdl == m_provisionalDocumentLoader) clearProvisionalLoad(); - else if (m_provisionalDocumentLoader) { - KURL unreachableURL = m_provisionalDocumentLoader->unreachableURL(); + else if (activeDocumentLoader()) { + KURL unreachableURL = activeDocumentLoader()->unreachableURL(); if (!unreachableURL.isEmpty() && unreachableURL == pdl->request().url()) shouldReset = false; } @@ -3339,10 +3375,16 @@ unsigned long FrameLoader::loadResourceSynchronously(const ResourceRequest& requ ResourceRequest initialRequest = request; initialRequest.setTimeoutInterval(10); + // Use the original request's cache policy for two reasons: + // 1. For POST requests, we mutate the cache policy for the main resource, + // but we do not want this to apply to subresources + // 2. Delegates that modify the cache policy using willSendRequest: should + // not affect any other resources. Such changes need to be done + // per request. if (initialRequest.isConditional()) initialRequest.setCachePolicy(ReloadIgnoringCacheData); else - initialRequest.setCachePolicy(documentLoader()->request().cachePolicy()); + initialRequest.setCachePolicy(originalRequest().cachePolicy()); if (!referrer.isEmpty()) initialRequest.setHTTPReferrer(referrer); @@ -3423,12 +3465,12 @@ void FrameLoader::callContinueFragmentScrollAfterNavigationPolicy(void* argument void FrameLoader::continueFragmentScrollAfterNavigationPolicy(const ResourceRequest& request, bool shouldContinue) { - bool isRedirect = m_quickRedirectComing || policyChecker()->loadType() == FrameLoadTypeRedirectWithLockedBackForwardList; m_quickRedirectComing = false; if (!shouldContinue) return; +<<<<<<< HEAD:WebCore/loader/FrameLoader.cpp KURL url = request.url(); m_documentLoader->replaceRequestURLForAnchorScroll(url); @@ -3464,6 +3506,10 @@ void FrameLoader::continueFragmentScrollAfterNavigationPolicy(const ResourceRequ m_client->dispatchDidChangeLocationWithinPage(); m_client->didFinishLoad(); +======= + bool isRedirect = m_quickRedirectComing || policyChecker()->loadType() == FrameLoadTypeRedirectWithLockedBackForwardList; + loadInSameDocument(request.url(), 0, !isRedirect); +>>>>>>> webkit.org at r51976:WebCore/loader/FrameLoader.cpp } bool FrameLoader::shouldScrollToAnchor(bool isFormSubmission, FrameLoadType loadType, const KURL& url) @@ -3738,14 +3784,51 @@ Frame* FrameLoader::findFrameForNavigation(const AtomicString& name) return frame; } -// Loads content into this frame, as specified by history item +void FrameLoader::navigateWithinDocument(HistoryItem* item) +{ + ASSERT(!item->document() || item->document() == m_frame->document()); + + // Save user view state to the current history item here since we don't do a normal load. + // FIXME: Does form state need to be saved here too? + history()->saveScrollPositionAndViewStateToItem(history()->currentItem()); + if (FrameView* view = m_frame->view()) + view->setWasScrolledByUser(false); + + history()->setCurrentItem(item); + + // loadInSameDocument() actually changes the URL and notifies load delegates of a "fake" load + loadInSameDocument(item->url(), item->stateObject(), false); + + // Restore user view state from the current history item here since we don't do a normal load. + // Even though we just manually set the current history item, this ASSERT verifies nothing + // inside of loadInSameDocument() caused it to change. + ASSERT(history()->currentItem() == item); + history()->restoreScrollPositionAndViewState(); +} + // FIXME: This function should really be split into a couple pieces, some of // which should be methods of HistoryController and some of which should be // methods of FrameLoader. -void FrameLoader::loadItem(HistoryItem* item, FrameLoadType loadType) +void FrameLoader::navigateToDifferentDocument(HistoryItem* item, FrameLoadType loadType) { - if (!m_frame->page()) - return; + // Remember this item so we can traverse any child items as child frames load + history()->setProvisionalItem(item); + + // Check if we'll be using the page cache. We only use the page cache + // if one exists and it is less than _backForwardCacheExpirationInterval + // seconds old. If the cache is expired it gets flushed here. + if (RefPtr cachedPage = pageCache()->get(item)) { + // FIXME: 1800 should not be hardcoded, it should come from + // WebKitBackForwardCacheExpirationIntervalKey in WebKit. + // Or we should remove WebKitBackForwardCacheExpirationIntervalKey. + if (currentTime() - cachedPage->timeStamp() <= 1800) { + loadWithDocumentLoader(cachedPage->documentLoader(), loadType, 0); + return; + } + + LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", history()->provisionalItem()->url().string().ascii().data()); + pageCache()->remove(item); + } KURL itemURL = item->url(); KURL itemOriginalURL = item->originalURL(); @@ -3754,138 +3837,96 @@ void FrameLoader::loadItem(HistoryItem* item, FrameLoadType loadType) currentURL = documentLoader()->url(); RefPtr formData = item->formData(); - // Are we navigating to an anchor within the page? - // Note if we have child frames we do a real reload, since the child frames might not - // match our current frame structure, or they might not have the right content. We could - // check for all that as an additional optimization. - // We also do not do anchor-style navigation if we're posting a form or navigating from - // a page that was resulted from a form post. - bool shouldScroll = !formData && !(history()->currentItem() && history()->currentItem()->formData()) && history()->urlsMatchItem(item); + bool addedExtraFields = false; + ResourceRequest request(itemURL); -#if ENABLE(WML) - // All WML decks should go through the real load mechanism, not the scroll-to-anchor code - if (frameContainsWMLContent(m_frame)) - shouldScroll = false; -#endif - - if (shouldScroll) { - // Must do this maintenance here, since we don't go through a real page reload - history()->saveScrollPositionAndViewStateToItem(history()->currentItem()); - - if (FrameView* view = m_frame->view()) - view->setWasScrolledByUser(false); - - history()->setCurrentItem(item); - - // FIXME: Form state might need to be saved here too. - - // We always call scrollToAnchor here, even if the URL doesn't have an - // anchor fragment. This is so we'll keep the WebCore Frame's URL up-to-date. - scrollToAnchor(item->url()); + if (!item->referrer().isNull()) + request.setHTTPReferrer(item->referrer()); - // must do this maintenance here, since we don't go through a real page reload - history()->restoreScrollPositionAndViewState(); + // If this was a repost that failed the page cache, we might try to repost the form. + NavigationAction action; + if (formData) { + formData->generateFiles(m_frame->page()->chrome()->client()); + + request.setHTTPMethod("POST"); + request.setHTTPBody(formData); + request.setHTTPContentType(item->formContentType()); + RefPtr securityOrigin = SecurityOrigin::createFromString(item->referrer()); + addHTTPOriginIfNeeded(request, securityOrigin->toString()); + + // Make sure to add extra fields to the request after the Origin header is added for the FormData case. + // See https://bugs.webkit.org/show_bug.cgi?id=22194 for more discussion. + addExtraFieldsToRequest(request, m_loadType, true, formData); + addedExtraFields = true; - // Fake the URL change by updating the data source's request. This will no longer - // be necessary if we do the better fix described above. - documentLoader()->replaceRequestURLForAnchorScroll(itemURL); - - m_client->dispatchDidChangeLocationWithinPage(); + // FIXME: Slight hack to test if the NSURL cache contains the page we're going to. + // We want to know this before talking to the policy delegate, since it affects whether + // we show the DoYouReallyWantToRepost nag. + // + // This trick has a small bug (3123893) where we might find a cache hit, but then + // have the item vanish when we try to use it in the ensuing nav. This should be + // extremely rare, but in that case the user will get an error on the navigation. - // FrameLoaderClient::didFinishLoad() tells the internal load delegate the load finished with no error - m_client->didFinishLoad(); + if (ResourceHandle::willLoadFromCache(request, m_frame)) + action = NavigationAction(itemURL, loadType, false); + else { + request.setCachePolicy(ReloadIgnoringCacheData); + action = NavigationAction(itemURL, NavigationTypeFormResubmitted); + } } else { - // Remember this item so we can traverse any child items as child frames load - history()->setProvisionalItem(item); - - bool inPageCache = false; - - // Check if we'll be using the page cache. We only use the page cache - // if one exists and it is less than _backForwardCacheExpirationInterval - // seconds old. If the cache is expired it gets flushed here. - if (RefPtr cachedPage = pageCache()->get(item)) { - double interval = currentTime() - cachedPage->timeStamp(); - - // FIXME: 1800 should not be hardcoded, it should come from - // WebKitBackForwardCacheExpirationIntervalKey in WebKit. - // Or we should remove WebKitBackForwardCacheExpirationIntervalKey. - if (interval <= 1800) { - loadWithDocumentLoader(cachedPage->documentLoader(), loadType, 0); - inPageCache = true; - } else { - LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", history()->provisionalItem()->url().string().ascii().data()); - pageCache()->remove(item); - } + switch (loadType) { + case FrameLoadTypeReload: + case FrameLoadTypeReloadFromOrigin: + request.setCachePolicy(ReloadIgnoringCacheData); + break; + case FrameLoadTypeBack: + case FrameLoadTypeBackWMLDeckNotAccessible: + case FrameLoadTypeForward: + case FrameLoadTypeIndexedBackForward: + if (!itemURL.protocolIs("https")) + request.setCachePolicy(ReturnCacheDataElseLoad); + break; + case FrameLoadTypeStandard: + case FrameLoadTypeRedirectWithLockedBackForwardList: + break; + case FrameLoadTypeSame: + default: + ASSERT_NOT_REACHED(); } - - if (!inPageCache) { - bool addedExtraFields = false; - ResourceRequest request(itemURL); - if (!item->referrer().isNull()) - request.setHTTPReferrer(item->referrer()); - - // If this was a repost that failed the page cache, we might try to repost the form. - NavigationAction action; - if (formData) { - - formData->generateFiles(m_frame->page()->chrome()->client()); + action = NavigationAction(itemOriginalURL, loadType, false); + } + + if (!addedExtraFields) + addExtraFieldsToRequest(request, m_loadType, true, formData); - request.setHTTPMethod("POST"); - request.setHTTPBody(formData); - request.setHTTPContentType(item->formContentType()); - RefPtr securityOrigin = SecurityOrigin::createFromString(item->referrer()); - addHTTPOriginIfNeeded(request, securityOrigin->toString()); - - // Make sure to add extra fields to the request after the Origin header is added for the FormData case. - // See https://bugs.webkit.org/show_bug.cgi?id=22194 for more discussion. - addExtraFieldsToRequest(request, m_loadType, true, formData); - addedExtraFields = true; - - // FIXME: Slight hack to test if the NSURL cache contains the page we're going to. - // We want to know this before talking to the policy delegate, since it affects whether - // we show the DoYouReallyWantToRepost nag. - // - // This trick has a small bug (3123893) where we might find a cache hit, but then - // have the item vanish when we try to use it in the ensuing nav. This should be - // extremely rare, but in that case the user will get an error on the navigation. - - if (ResourceHandle::willLoadFromCache(request, m_frame)) - action = NavigationAction(itemURL, loadType, false); - else { - request.setCachePolicy(ReloadIgnoringCacheData); - action = NavigationAction(itemURL, NavigationTypeFormResubmitted); - } - } else { - switch (loadType) { - case FrameLoadTypeReload: - case FrameLoadTypeReloadFromOrigin: - request.setCachePolicy(ReloadIgnoringCacheData); - break; - case FrameLoadTypeBack: - case FrameLoadTypeBackWMLDeckNotAccessible: - case FrameLoadTypeForward: - case FrameLoadTypeIndexedBackForward: - if (itemURL.protocol() != "https") - request.setCachePolicy(ReturnCacheDataElseLoad); - break; - case FrameLoadTypeStandard: - case FrameLoadTypeRedirectWithLockedBackForwardList: - break; - case FrameLoadTypeSame: - default: - ASSERT_NOT_REACHED(); - } + loadWithNavigationAction(request, action, false, loadType, 0); +} - action = NavigationAction(itemOriginalURL, loadType, false); - } - - if (!addedExtraFields) - addExtraFieldsToRequest(request, m_loadType, true, formData); +// Loads content into this frame, as specified by history item +void FrameLoader::loadItem(HistoryItem* item, FrameLoadType loadType) +{ + // We do same-document navigation in the following cases: + // - The HistoryItem has a history state object + // - Navigating to an anchor within the page, with no form data stored on the target item or the current history entry, + // and the URLs in the frame tree match the history item for fragment scrolling. + bool sameDocumentNavigation = (!item->formData() && !(history()->currentItem() && history()->currentItem()->formData()) && history()->urlsMatchItem(item)) || item->document() == m_frame->document(); - loadWithNavigationAction(request, action, false, loadType, 0); - } - } +#if ENABLE(WML) + // All WML decks should go through the real load mechanism, not the scroll-to-anchor code + // FIXME: Why do WML decks have this different behavior? + // Are WML decks incompatible with HTML5 pushState/replaceState which require inter-document history navigations? + // Should this new API be disabled for WML pages, or does WML need to update their mechanism to act like normal loads? + // If scroll-to-anchor navigations were broken for WML and required them to have different loading behavior, then + // state object loads are certainly also broken for them. + if (frameContainsWMLContent(m_frame)) + sameDocumentNavigation = false; +#endif + + if (sameDocumentNavigation) + navigateWithinDocument(item); + else + navigateToDifferentDocument(item, loadType); } void FrameLoader::setMainDocumentError(DocumentLoader* loader, const ResourceError& error) @@ -3955,15 +3996,28 @@ void FrameLoader::dispatchDocumentElementAvailable() m_client->documentElementAvailable(); } -void FrameLoader::dispatchWindowObjectAvailable() +void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() { - // FIXME: should this be isolated-worlds-aware? - if (!m_frame->script()->isEnabled() || !m_frame->script()->existingWindowShell(mainThreadNormalWorld())) + if (!m_frame->script()->isEnabled()) return; - m_client->windowObjectCleared(); + Vector worlds; + ScriptController::getAllWorlds(worlds); + for (size_t i = 0; i < worlds.size(); ++i) + dispatchDidClearWindowObjectInWorld(worlds[i]); +} + +void FrameLoader::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld* world) +{ + if (!m_frame->script()->isEnabled() || !m_frame->script()->existingWindowShell(world)) + return; + + m_client->dispatchDidClearWindowObjectInWorld(world); #if ENABLE(INSPECTOR) + if (world != mainThreadNormalWorld()) + return; + if (Page* page = m_frame->page()) { if (InspectorController* inspector = page->inspectorController()) inspector->inspectedWindowScriptObjectCleared(m_frame); @@ -3973,6 +4027,25 @@ void FrameLoader::dispatchWindowObjectAvailable() #endif } +void FrameLoader::updateSandboxFlags() +{ + SandboxFlags flags = SandboxNone; + if (Frame* parentFrame = m_frame->tree()->parent()) + flags |= parentFrame->loader()->sandboxFlags(); + if (HTMLFrameOwnerElement* ownerElement = m_frame->ownerElement()) + flags |= ownerElement->sandboxFlags(); + + if (m_sandboxFlags == flags) + return; + + m_sandboxFlags = flags; + + m_frame->document()->updateSandboxFlags(); + + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->loader()->updateSandboxFlags(); + } + PassRefPtr FrameLoader::createJavaAppletWidget(const IntSize& size, HTMLAppletElement* element, const HashMap& args) { String baseURLString; -- cgit v1.1