1/* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "PageGroup.h" 28 29#include "Chrome.h" 30#include "ChromeClient.h" 31#include "Document.h" 32#include "Frame.h" 33#include "GroupSettings.h" 34#include "IDBFactoryBackendInterface.h" 35#include "Page.h" 36#include "PageCache.h" 37#include "SecurityOrigin.h" 38#include "Settings.h" 39#include "StorageNamespace.h" 40 41#if PLATFORM(CHROMIUM) 42#include "PlatformBridge.h" 43#endif 44 45#ifdef ANDROID 46#include "DOMWindow.h" 47#include "FileSystem.h" 48#endif 49 50namespace WebCore { 51 52static unsigned getUniqueIdentifier() 53{ 54 static unsigned currentIdentifier = 0; 55 return ++currentIdentifier; 56} 57 58// -------- 59 60static bool shouldTrackVisitedLinks = false; 61 62PageGroup::PageGroup(const String& name) 63 : m_name(name) 64 , m_visitedLinksPopulated(false) 65 , m_identifier(getUniqueIdentifier()) 66 , m_groupSettings(GroupSettings::create()) 67{ 68} 69 70PageGroup::PageGroup(Page* page) 71 : m_visitedLinksPopulated(false) 72 , m_identifier(getUniqueIdentifier()) 73 , m_groupSettings(GroupSettings::create()) 74{ 75 ASSERT(page); 76 addPage(page); 77} 78 79PageGroup::~PageGroup() 80{ 81 removeAllUserContent(); 82} 83 84typedef HashMap<String, PageGroup*> PageGroupMap; 85static PageGroupMap* pageGroups = 0; 86 87PageGroup* PageGroup::pageGroup(const String& groupName) 88{ 89 ASSERT(!groupName.isEmpty()); 90 91 if (!pageGroups) 92 pageGroups = new PageGroupMap; 93 94 pair<PageGroupMap::iterator, bool> result = pageGroups->add(groupName, 0); 95 96 if (result.second) { 97 ASSERT(!result.first->second); 98 result.first->second = new PageGroup(groupName); 99 } 100 101 ASSERT(result.first->second); 102 return result.first->second; 103} 104 105void PageGroup::closeLocalStorage() 106{ 107#if ENABLE(DOM_STORAGE) 108 if (!pageGroups) 109 return; 110 111 PageGroupMap::iterator end = pageGroups->end(); 112 113 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) { 114 if (it->second->hasLocalStorage()) 115 it->second->localStorage()->close(); 116 } 117#endif 118} 119 120#if ENABLE(DOM_STORAGE) 121 122void PageGroup::clearLocalStorageForAllOrigins() 123{ 124 if (!pageGroups) 125 return; 126 127 PageGroupMap::iterator end = pageGroups->end(); 128 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) { 129 if (it->second->hasLocalStorage()) 130 it->second->localStorage()->clearAllOriginsForDeletion(); 131 } 132} 133 134void PageGroup::clearLocalStorageForOrigin(SecurityOrigin* origin) 135{ 136 if (!pageGroups) 137 return; 138 139 PageGroupMap::iterator end = pageGroups->end(); 140 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) { 141 if (it->second->hasLocalStorage()) 142 it->second->localStorage()->clearOriginForDeletion(origin); 143 } 144} 145 146void PageGroup::syncLocalStorage() 147{ 148 if (!pageGroups) 149 return; 150 151 PageGroupMap::iterator end = pageGroups->end(); 152 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) { 153 if (it->second->hasLocalStorage()) 154 it->second->localStorage()->sync(); 155 } 156} 157 158unsigned PageGroup::numberOfPageGroups() 159{ 160 if (!pageGroups) 161 return 0; 162 163 return pageGroups->size(); 164} 165 166#if defined(ANDROID) 167void PageGroup::clearDomStorage() 168{ 169 if (!pageGroups) 170 return; 171 172 PageGroupMap::iterator end = pageGroups->end(); 173 174 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) { 175 String basePath = ""; 176 177 // This is being called as a result of the user explicitly 178 // asking to clear all stored data (e.g. through a settings 179 // dialog. We need a page in the page group to fire a 180 // StorageEvent. There isn't really a correct page to use 181 // as the source (as the clear request hasn't come from a 182 // particular page). One thing we should ensure though is that 183 // we don't try to clear a private browsing mode page as that has no concept 184 // of DOM storage.. 185 186 HashSet<Page*> pages = it->second->pages(); 187 HashSet<Page*>::iterator pagesEnd = pages.end(); 188 Page* page = 0; 189 for(HashSet<Page*>::iterator pit = pages.begin(); pit != pagesEnd; ++pit) { 190 Page* p = *pit; 191 192 // Grab the storage location from an arbitrary page. This is set 193 // to the same value on all private browsing and "normal" pages, 194 // so we can get it from anything. 195 if (basePath.isEmpty()) 196 basePath = p->settings()->localStorageDatabasePath(); 197 198 // DOM storage is disabled in private browsing pages, so nothing to do if 199 // this is such a page. 200 if (p->settings()->privateBrowsingEnabled()) 201 continue; 202 203 // Clear session storage. 204 StorageNamespace* sessionStorage = p->sessionStorage(false); 205 if (sessionStorage) 206 sessionStorage->clear(p); 207 208 // Save this page so we can clear local storage. 209 page = p; 210 } 211 212 // If page is still null at this point, then the only pages that are 213 // open are private browsing pages. Hence no pages are currently using local 214 // storage, so we don't need a page pointer to send any events and the 215 // clear function will handle a 0 input. 216 it->second->localStorage()->clear(page); 217 it->second->localStorage()->close(); 218 219 // Closing the storage areas will stop the background thread and so 220 // we need to remove the local storage ref here so that next time 221 // we come to a site that uses it the thread will get started again. 222 it->second->removeLocalStorage(); 223 224 // At this point, active local and session storage have been cleared and the 225 // StorageAreas for this PageGroup closed. The final sync will have taken 226 // place. All that is left is to purge the database files. 227 if (!basePath.isEmpty()) { 228 Vector<String> files = listDirectory(basePath, "*.localstorage"); 229 Vector<String>::iterator filesEnd = files.end(); 230 for (Vector<String>::iterator it = files.begin(); it != filesEnd; ++it) 231 deleteFile(*it); 232 } 233 } 234} 235 236void PageGroup::removeLocalStorage() 237{ 238 HashSet<Page*> pages = this->pages(); 239 HashSet<Page*>::iterator pagesEnd = pages.end(); 240 for(HashSet<Page*>::iterator pit = pages.begin(); pit != pagesEnd; ++pit) { 241 Page* p = *pit; 242 for (Frame* frame = p->mainFrame(); frame; frame = frame->tree()->traverseNext()) 243 frame->document()->domWindow()->clearDOMStorage(); 244 } 245 246 m_localStorage = 0; 247} 248#endif // PLATFORM(ANDROID) 249 250#endif 251 252void PageGroup::addPage(Page* page) 253{ 254 ASSERT(page); 255 ASSERT(!m_pages.contains(page)); 256 m_pages.add(page); 257} 258 259void PageGroup::removePage(Page* page) 260{ 261 ASSERT(page); 262 ASSERT(m_pages.contains(page)); 263 m_pages.remove(page); 264} 265 266bool PageGroup::isLinkVisited(LinkHash visitedLinkHash) 267{ 268#if PLATFORM(CHROMIUM) 269 // Use Chromium's built-in visited link database. 270 return PlatformBridge::isLinkVisited(visitedLinkHash); 271#else 272 if (!m_visitedLinksPopulated) { 273 m_visitedLinksPopulated = true; 274 ASSERT(!m_pages.isEmpty()); 275 (*m_pages.begin())->chrome()->client()->populateVisitedLinks(); 276 } 277 return m_visitedLinkHashes.contains(visitedLinkHash); 278#endif 279} 280 281void PageGroup::addVisitedLinkHash(LinkHash hash) 282{ 283 if (shouldTrackVisitedLinks) 284 addVisitedLink(hash); 285} 286 287inline void PageGroup::addVisitedLink(LinkHash hash) 288{ 289 ASSERT(shouldTrackVisitedLinks); 290#if !PLATFORM(CHROMIUM) 291 if (!m_visitedLinkHashes.add(hash).second) 292 return; 293#endif 294 Page::visitedStateChanged(this, hash); 295 pageCache()->markPagesForVistedLinkStyleRecalc(); 296} 297 298void PageGroup::addVisitedLink(const KURL& url) 299{ 300 if (!shouldTrackVisitedLinks) 301 return; 302 ASSERT(!url.isEmpty()); 303 addVisitedLink(visitedLinkHash(url.string().characters(), url.string().length())); 304} 305 306void PageGroup::addVisitedLink(const UChar* characters, size_t length) 307{ 308 if (!shouldTrackVisitedLinks) 309 return; 310 addVisitedLink(visitedLinkHash(characters, length)); 311} 312 313void PageGroup::removeVisitedLinks() 314{ 315 m_visitedLinksPopulated = false; 316 if (m_visitedLinkHashes.isEmpty()) 317 return; 318 m_visitedLinkHashes.clear(); 319 Page::allVisitedStateChanged(this); 320 pageCache()->markPagesForVistedLinkStyleRecalc(); 321} 322 323void PageGroup::removeAllVisitedLinks() 324{ 325 Page::removeAllVisitedLinks(); 326 pageCache()->markPagesForVistedLinkStyleRecalc(); 327} 328 329void PageGroup::setShouldTrackVisitedLinks(bool shouldTrack) 330{ 331 if (shouldTrackVisitedLinks == shouldTrack) 332 return; 333 shouldTrackVisitedLinks = shouldTrack; 334 if (!shouldTrackVisitedLinks) 335 removeAllVisitedLinks(); 336} 337 338#if ENABLE(DOM_STORAGE) 339StorageNamespace* PageGroup::localStorage() 340{ 341 if (!m_localStorage) { 342 // Need a page in this page group to query the settings for the local storage database path. 343 // Having these parameters attached to the page settings is unfortunate since these settings are 344 // not per-page (and, in fact, we simply grab the settings from some page at random), but 345 // at this point we're stuck with it. 346 Page* page = *m_pages.begin(); 347 const String& path = page->settings()->localStorageDatabasePath(); 348 unsigned quota = m_groupSettings->localStorageQuotaBytes(); 349 m_localStorage = StorageNamespace::localStorageNamespace(path, quota); 350 } 351 352 return m_localStorage.get(); 353} 354 355#endif 356 357#if ENABLE(INDEXED_DATABASE) 358IDBFactoryBackendInterface* PageGroup::idbFactory() 359{ 360 // Do not add page setting based access control here since this object is shared by all pages in 361 // the group and having per-page controls is misleading. 362 if (!m_factoryBackend) 363 m_factoryBackend = IDBFactoryBackendInterface::create(); 364 return m_factoryBackend.get(); 365} 366#endif 367 368void PageGroup::addUserScriptToWorld(DOMWrapperWorld* world, const String& source, const KURL& url, 369 PassOwnPtr<Vector<String> > whitelist, PassOwnPtr<Vector<String> > blacklist, 370 UserScriptInjectionTime injectionTime, UserContentInjectedFrames injectedFrames) 371{ 372 ASSERT_ARG(world, world); 373 374 OwnPtr<UserScript> userScript(new UserScript(source, url, whitelist, blacklist, injectionTime, injectedFrames)); 375 if (!m_userScripts) 376 m_userScripts.set(new UserScriptMap); 377 UserScriptVector*& scriptsInWorld = m_userScripts->add(world, 0).first->second; 378 if (!scriptsInWorld) 379 scriptsInWorld = new UserScriptVector; 380 scriptsInWorld->append(userScript.release()); 381} 382 383void PageGroup::addUserStyleSheetToWorld(DOMWrapperWorld* world, const String& source, const KURL& url, 384 PassOwnPtr<Vector<String> > whitelist, PassOwnPtr<Vector<String> > blacklist, 385 UserContentInjectedFrames injectedFrames, 386 UserStyleLevel level, 387 UserStyleInjectionTime injectionTime) 388{ 389 ASSERT_ARG(world, world); 390 391 OwnPtr<UserStyleSheet> userStyleSheet(new UserStyleSheet(source, url, whitelist, blacklist, injectedFrames, level)); 392 if (!m_userStyleSheets) 393 m_userStyleSheets.set(new UserStyleSheetMap); 394 UserStyleSheetVector*& styleSheetsInWorld = m_userStyleSheets->add(world, 0).first->second; 395 if (!styleSheetsInWorld) 396 styleSheetsInWorld = new UserStyleSheetVector; 397 styleSheetsInWorld->append(userStyleSheet.release()); 398 399 if (injectionTime == InjectInExistingDocuments) 400 resetUserStyleCacheInAllFrames(); 401} 402 403void PageGroup::removeUserScriptFromWorld(DOMWrapperWorld* world, const KURL& url) 404{ 405 ASSERT_ARG(world, world); 406 407 if (!m_userScripts) 408 return; 409 410 UserScriptMap::iterator it = m_userScripts->find(world); 411 if (it == m_userScripts->end()) 412 return; 413 414 UserScriptVector* scripts = it->second; 415 for (int i = scripts->size() - 1; i >= 0; --i) { 416 if (scripts->at(i)->url() == url) 417 scripts->remove(i); 418 } 419 420 if (!scripts->isEmpty()) 421 return; 422 423 delete it->second; 424 m_userScripts->remove(it); 425} 426 427void PageGroup::removeUserStyleSheetFromWorld(DOMWrapperWorld* world, const KURL& url) 428{ 429 ASSERT_ARG(world, world); 430 431 if (!m_userStyleSheets) 432 return; 433 434 UserStyleSheetMap::iterator it = m_userStyleSheets->find(world); 435 bool sheetsChanged = false; 436 if (it == m_userStyleSheets->end()) 437 return; 438 439 UserStyleSheetVector* stylesheets = it->second; 440 for (int i = stylesheets->size() - 1; i >= 0; --i) { 441 if (stylesheets->at(i)->url() == url) { 442 stylesheets->remove(i); 443 sheetsChanged = true; 444 } 445 } 446 447 if (!sheetsChanged) 448 return; 449 450 if (!stylesheets->isEmpty()) { 451 delete it->second; 452 m_userStyleSheets->remove(it); 453 } 454 455 resetUserStyleCacheInAllFrames(); 456} 457 458void PageGroup::removeUserScriptsFromWorld(DOMWrapperWorld* world) 459{ 460 ASSERT_ARG(world, world); 461 462 if (!m_userScripts) 463 return; 464 465 UserScriptMap::iterator it = m_userScripts->find(world); 466 if (it == m_userScripts->end()) 467 return; 468 469 delete it->second; 470 m_userScripts->remove(it); 471} 472 473void PageGroup::removeUserStyleSheetsFromWorld(DOMWrapperWorld* world) 474{ 475 ASSERT_ARG(world, world); 476 477 if (!m_userStyleSheets) 478 return; 479 480 UserStyleSheetMap::iterator it = m_userStyleSheets->find(world); 481 if (it == m_userStyleSheets->end()) 482 return; 483 484 delete it->second; 485 m_userStyleSheets->remove(it); 486 487 resetUserStyleCacheInAllFrames(); 488} 489 490void PageGroup::removeAllUserContent() 491{ 492 if (m_userScripts) { 493 deleteAllValues(*m_userScripts); 494 m_userScripts.clear(); 495 } 496 497 if (m_userStyleSheets) { 498 deleteAllValues(*m_userStyleSheets); 499 m_userStyleSheets.clear(); 500 resetUserStyleCacheInAllFrames(); 501 } 502} 503 504void PageGroup::resetUserStyleCacheInAllFrames() 505{ 506 // Clear our cached sheets and have them just reparse. 507 HashSet<Page*>::const_iterator end = m_pages.end(); 508 for (HashSet<Page*>::const_iterator it = m_pages.begin(); it != end; ++it) { 509 for (Frame* frame = (*it)->mainFrame(); frame; frame = frame->tree()->traverseNext()) 510 frame->document()->updatePageGroupUserSheets(); 511 } 512} 513 514} // namespace WebCore 515